Compare commits

...

8 Commits

Author SHA1 Message Date
Craig White
fa64df531f Rework autofitter to center tildes if they are small. 2024-10-20 05:24:16 -04:00
Werner Lemberg
6bc1ac488b * docs/CHANGES: Mention Craig's GSoC 2023 project. 2024-07-18 09:50:33 +02:00
Craig White
faae5501ca [autofit] Add GSUB table handling to reverse character map generation.
If HarfBuzz is enabled, the reverse character map generation now considers
GSUB entries when looking for glyphs that correspond to a code point.

* src/autofit/afadjust.c (af_all_glyph_variants_helper,
af_all_glyph_variants) [FT_CONFIG_OPTION_USE_HARFBUZZ]: New functions.

(af_reverse_character_map_new) [FT_CONFIG_OPTION_USE_HARFBUZZ]: Call new
code.
2024-07-18 09:48:43 +02:00
Craig White
adf5129ad9 [autofit] Add tilde-unflattening algorithm.
* src/autofit/aflatin.c (af_find_highest_contour,
af_remove_segments_containing_point,
af_latin_remove_tilde_points_from_edges, af_latin_stretch_tildes,
af_latin_align_tildes): New functions.
(af_latin_hints_apply): Call tilde-unflatting code if necessary.
2024-07-18 09:48:43 +02:00
Craig White
1e5e193adb [autofit] Implement vertical separation adjustment.
* src/autofit/aflatin.c: Include `afadjust.h`.
(af_latin_metrics_init): Call `af_reverse_character_map_new`.
(af_latin_metrics_done): New function.

(af_move_contour_vertically, af_check_contour_horizontal_overlap,
af_glyph_hints_apply_vertical_separation_adjustments): New functions.

(af_latin_hints_apply): Call
`af_glyph_hints_apply_vertical_separation_adjustments`.

(af_latin_writing_system_class): Updated.

* src/autofit/aftypes.h (AF_StyleMetricsRec): Add `reverse_charmap` field.
2024-07-18 09:48:43 +02:00
Craig White
c70b3d9484 [autofit] Add code for reverse charmaps and adjustment database lookup.
* src/autofit/aftypes.h (AF_ReverseMapEntry, AF_ReverseCharacterMap): New
structures.

* src/autofit/afadjust.c (af_adjustment_database_lookup,
af_reverse_character_map_entry_compare, af_reverse_character_map_lookup,
af_lookup_vertical_separation_type, af_lookup_tilde_correction_type,
af_reverse_character_map_expand, af_reverse_character_map_new,
af_reverse_character_map_done): New functions.

* src/autofit/afadjust.c: Updated.
2024-07-18 09:48:43 +02:00
Craig White
fe31700701 [autofit] Add adjustment database.
* src/autofit/afadjust.c, src/autofit/afadjust.h: New files.

* include/freetype/internal/fttrace.h: Add 'afadjust' component.

* src/autofit/autofit.c: Include `afadjust.c`.

* src/autofit/rules.mk (AUTOF_DRV_SRC): Add `afadjust.c`.
2024-07-18 09:48:43 +02:00
Craig White
efa555bfe6 [base] Make find_unicode_charmap a base function.
This is needed for forthcoming changes in the auto-hinter.

* include/freetype/internal/ftobjs.h, src/base/ftobjs.c: Updated.
2024-07-18 09:48:43 +02:00
10 changed files with 1707 additions and 17 deletions

View File

@ -7,6 +7,21 @@ CHANGES BETWEEN 2.13.2 and 2.13.3 (2024-Mmm-DD)
usage. It is also an additional means to protect against
malformed input.
- The auto-hinter got new abilities.
. It can now better separate accents from base characters at small
sizes by artificially moving accents up if necessary.
. Tilde characters get vertically stretched at small sizes so that
they don't degenerate to horizontal lines.
Both features use a database, which currently has entries for
Unicode characters lower than U+0180. FreeType needs to access or
construct a proper Unicode character map from a given font to make
this work.
This was Craig White's GSoC 2023 project.
II. IMPORTANT BUG FIXES

View File

@ -275,6 +275,28 @@ FT_BEGIN_HEADER
FT_GlyphSlot slot,
FT_Render_Mode mode );
/**************************************************************************
*
* @Function:
* find_unicode_charmap
*
* @Description:
* This function finds a Unicode charmap, if there is one. And if there
* is more than one, it tries to favour the more extensive one, i.e., one
* that supports UCS-4 against those which are limited to the BMP (UCS-2
* encoding.)
*
* If a unicode charmap is found, `face->charmap` is set to it.
*
* This function is called from `open_face`, from `FT_Select_Charmap(...,
* FT_ENCODING_UNICODE)`, and also from `afadjust.c` in the 'autofit'
* module.
*/
FT_BASE( FT_Error )
find_unicode_charmap( FT_Face face );
#ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING
typedef void (*FT_Bitmap_LcdFilterFunc)( FT_Bitmap* bitmap,

View File

@ -159,6 +159,7 @@ FT_TRACE_DEF( gxvprop )
FT_TRACE_DEF( gxvtrak )
/* autofit components */
FT_TRACE_DEF( afadjust )
FT_TRACE_DEF( afcjk )
FT_TRACE_DEF( afglobal )
FT_TRACE_DEF( afhints )

843
src/autofit/afadjust.c Normal file
View File

@ -0,0 +1,843 @@
/****************************************************************************
*
* afadjust.c
*
* Auto-fitter routines to adjust components based on charcode (body).
*
* Copyright (C) 2023-2024 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* Written by Craig White <gerzytet@gmail.com>.
*
* This file is part of the FreeType project, and may only be used,
* modified, and distributed under the terms of the FreeType project
* license, LICENSE.TXT. By continuing to use, modify, or distribute
* this file you indicate that you have read the license and
* understand and accept it fully.
*
*/
#include "afadjust.h"
#include <freetype/freetype.h>
#include <freetype/internal/ftobjs.h>
#include <freetype/internal/ftmemory.h>
#include <freetype/internal/ftdebug.h>
#define AF_ADJUSTMENT_DATABASE_LENGTH \
( sizeof ( adjustment_database ) / \
sizeof ( adjustment_database[0] ) )
#undef FT_COMPONENT
#define FT_COMPONENT afadjust
/*
All entries in this list must be sorted by ascending Unicode code
points. The table entries are 3 numbers consisting of:
- Unicode code point.
- The vertical adjustment type. This should be one of the enum
constants in `AF_VerticalSeparationAdjustmentType`.
- Value 1 if the topmost contour is a tilde and should be prevented from
flattening, and 0 otherwise.
*/
FT_LOCAL_ARRAY_DEF( AF_AdjustmentDatabaseEntry )
adjustment_database[] =
{
{ 0x21, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ! */
{ 0x69, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* i */
{ 0x6A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* j */
{ 0xA1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ¡ */
{ 0xBF, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ¿ */
{ 0xC0, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* À */
{ 0xC1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Á */
{ 0xC2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Â */
{ 0xC3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* Ã */
{ 0xC8, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* È */
{ 0xC9, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* É */
{ 0xCA, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ê */
{ 0xCC, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ì */
{ 0xCD, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Í */
{ 0xCE, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Î */
{ 0xD1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* Ñ */
{ 0xD2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ò */
{ 0xD3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ó */
{ 0xD4, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ô */
{ 0xD5, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* Õ */
{ 0xD9, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ù */
{ 0xDA, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ú */
{ 0xDB, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Û */
{ 0xDD, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ý */
{ 0xE0, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* à */
{ 0xE1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* á */
{ 0xE2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* â */
{ 0xE3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* ã */
{ 0xE8, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* è */
{ 0xE9, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* é */
{ 0xEA, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ê */
{ 0xEC, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ì */
{ 0xED, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* í */
{ 0xEE, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* î */
{ 0xF1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* ñ */
{ 0xF2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ò */
{ 0xF3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ó */
{ 0xF4, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ô */
{ 0xF5, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* õ */
{ 0xF9, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ù */
{ 0xFA, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ú */
{ 0xFB, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* û */
{ 0xFD, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ý */
{ 0x100, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ā */
{ 0x101, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ā */
{ 0x102, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ă */
{ 0x103, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ă */
{ 0x106, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ć */
{ 0x107, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ć */
{ 0x108, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĉ */
{ 0x109, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĉ */
{ 0x10A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ċ */
{ 0x10B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ċ */
{ 0x10C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Č */
{ 0x10D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* č */
{ 0x10E, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ď */
{ 0x112, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ē */
{ 0x113, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ē */
{ 0x114, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĕ */
{ 0x115, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĕ */
{ 0x116, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ė */
{ 0x117, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ė */
{ 0x11A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ě */
{ 0x11B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ě */
{ 0x11C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĝ */
{ 0x11D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĝ */
{ 0x11E, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ğ */
{ 0x11F, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ğ */
{ 0x120, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ġ */
{ 0x121, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ġ */
{ 0x123, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ģ */
{ 0x124, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĥ */
{ 0x125, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĥ */
{ 0x128, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* Ĩ */
{ 0x129, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* ĩ */
{ 0x12A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ī */
{ 0x12B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ī */
{ 0x12C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĭ */
{ 0x12D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĭ */
{ 0x12F, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* į */
{ 0x130, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* İ */
{ 0x133, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ij */
{ 0x134, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĵ */
{ 0x135, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĵ */
{ 0x139, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ĺ */
{ 0x13A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ĺ */
{ 0x143, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ń */
{ 0x144, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ń */
{ 0x147, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ň */
{ 0x148, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ň */
{ 0x14C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ō */
{ 0x14D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ō */
{ 0x14E, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŏ */
{ 0x14F, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŏ */
{ 0x154, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŕ */
{ 0x155, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŕ */
{ 0x158, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ř */
{ 0x159, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ř */
{ 0x15A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ś */
{ 0x15B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ś */
{ 0x15C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŝ */
{ 0x15D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŝ */
{ 0x160, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Š */
{ 0x161, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* š */
{ 0x164, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ť */
{ 0x168, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* Ũ */
{ 0x169, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 1 }, /* ũ */
{ 0x16A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ū */
{ 0x16B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ū */
{ 0x16C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŭ */
{ 0x16D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŭ */
{ 0x174, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŵ */
{ 0x175, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŵ */
{ 0x176, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ŷ */
{ 0x177, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ŷ */
{ 0x179, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ź */
{ 0x17A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ź */
{ 0x17B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ż */
{ 0x17C, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* ż */
{ 0x17D, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 }, /* Ž */
{ 0x17E, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP, 0 } /* ž */
};
/* Helper function: get the adjustment database entry for a codepoint. */
static const AF_AdjustmentDatabaseEntry*
af_adjustment_database_lookup( FT_UInt32 codepoint )
{
/* Binary search for database entry */
FT_Int low = 0;
FT_Int high = AF_ADJUSTMENT_DATABASE_LENGTH - 1;
while ( high >= low )
{
FT_Int mid = ( low + high ) / 2;
FT_UInt32 mid_codepoint = adjustment_database[mid].codepoint;
if ( mid_codepoint < codepoint )
low = mid + 1;
else if ( mid_codepoint > codepoint )
high = mid - 1;
else
return &adjustment_database[mid];
}
return NULL;
}
/* `qsort` compare function for reverse character map. */
FT_COMPARE_DEF( FT_Int )
af_reverse_character_map_entry_compare( const void *a,
const void *b )
{
const AF_ReverseMapEntry entry_a = *((const AF_ReverseMapEntry *)a);
const AF_ReverseMapEntry entry_b = *((const AF_ReverseMapEntry *)b);
return entry_a.glyph_index < entry_b.glyph_index
? -1
: entry_a.glyph_index > entry_b.glyph_index
? 1
: 0;
}
static FT_UInt32
af_reverse_character_map_lookup( AF_ReverseCharacterMap map,
FT_Int glyph_index )
{
FT_Int low, high;
FT_Long length;
if ( !map )
return 0;
length = map->length;
/* Binary search for reverse character map entry. */
low = 0;
high = length - 1;
while ( high >= low )
{
FT_Int mid = ( high + low ) / 2;
FT_Int mid_glyph_index = map->entries[mid].glyph_index;
if ( glyph_index < mid_glyph_index )
high = mid - 1;
else if ( glyph_index > mid_glyph_index )
low = mid + 1;
else
return map->entries[mid].codepoint;
}
return 0;
}
FT_LOCAL_DEF( AF_VerticalSeparationAdjustmentType )
af_lookup_vertical_separation_type( AF_ReverseCharacterMap map,
FT_Int glyph_index )
{
FT_UInt32 codepoint = af_reverse_character_map_lookup( map,
glyph_index );
const AF_AdjustmentDatabaseEntry *entry =
af_adjustment_database_lookup( codepoint );
if ( !entry )
return AF_VERTICAL_ADJUSTMENT_NONE;
return entry->vertical_separation_adjustment_type;
}
/* Return 1 if tilde correction should be applied to the topmost */
/* contour, else 0. */
FT_LOCAL_DEF( FT_Bool )
af_lookup_tilde_correction_type( AF_ReverseCharacterMap map,
FT_Int glyph_index )
{
FT_UInt32 codepoint = af_reverse_character_map_lookup( map,
glyph_index );
const AF_AdjustmentDatabaseEntry *entry =
af_adjustment_database_lookup( codepoint );
if ( !entry )
return 0;
return entry->apply_tilde;
}
/* Prepare to add one more entry to the reverse character map. */
/* This is a helper function for `af_reverse_character_map_new`. */
static FT_Error
af_reverse_character_map_expand( AF_ReverseCharacterMap map,
FT_Long *capacity,
FT_Memory memory )
{
FT_Error error;
if ( map->length < *capacity )
return FT_Err_Ok;
if ( map->length == *capacity )
{
FT_Long new_capacity = *capacity + *capacity / 2;
if ( FT_RENEW_ARRAY( map->entries, map->length, new_capacity ) )
return error;
*capacity = new_capacity;
}
return FT_Err_Ok;
}
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
/*
Recursive algorithm to find all glyphs that a code point could turn into
from the 'GSUB' table.
buffer: a buffer containing only the input code point
feature_tag_pool: the current list of features under consideration
current_features: the current list of features being applied
num_features: length of current_features
result: the set of glyphs that the input code point can map to
The algorithm works by running the `hb_ot_shape_glyphs_closure` function
on different lists of features to check which features map the glyph onto
something different. This function returns the result of transforming a
glyph using a list of features as well as all intermediate forms if the
glyph was transformed multiple times.
With no features enabled, `hb_ot_shape_glyphs_closure` only returns the
glyph given by the cmap. This character is the first to be placed into
the result set.
Next, the algorithm tests the same lookup enabling one feature at a time
and check whether any of those features change the result.
If any new glyph variants are found this way, they are added to the
result set and the algorithm recurses, trying that feature in combination
with every other feature to look for further glyph variants.
Example:
suppose we have the following features in the GSUB table:
f1: a -> b
f2: b -> c
f3: d -> e
The algorithm takes the following steps to find all variants of 'a'.
- Add 'a' to the result.
- Look up with feature list {f1}, yielding {a, b}.
=> Add 'b' to the result list, recurse.
- Look up with feature list {f1, f2}, yielding {a, b, c}.
=> Add 'c' to the result list, recurse.
- Look up with feature list {f1, f2, f3}, yielding {a, b, c}.
=> No new glyphs.
- Look up with feature list {f1, f3}, yielding {a, b}.
=> No new glyphs.
- Look up with feature list {f2}, yielding {a}.
=> No new glyphs.
- Look up with feature list {f3}, yielding {a}.
=> No new glyphs.
*/
static FT_Error
af_all_glyph_variants_helper( hb_font_t *font,
hb_buffer_t *buffer,
hb_set_t *feature_tag_pool,
hb_feature_t *current_features,
FT_UInt32 num_features,
hb_set_t* result )
{
FT_Error error;
hb_set_t *baseline_glyphs = NULL;
hb_set_t *new_glyphs = NULL;
hb_tag_t feature_tag;
/* Get the list of glyphs that are created by only transforming, */
/* based on the features in `current_features`. */
baseline_glyphs = hb_set_create();
if ( !hb_set_allocation_successful( baseline_glyphs ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
hb_ot_shape_glyphs_closure( font,
buffer,
current_features,
num_features,
baseline_glyphs );
if ( !hb_set_allocation_successful( baseline_glyphs ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
/* Add these baseline glyphs to the result. The baseline glyph set */
/* contains at least the glyph specified by the cmap. */
hb_set_union( result, baseline_glyphs );
if ( !hb_set_allocation_successful( result ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
if ( !hb_set_get_population( feature_tag_pool ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
/* Prepare for adding different features to `current_features` to */
/* check whether any of them have an effect of the glyphs we get from */
/* `hb_ot_shape_glyphs_closure`. */
current_features[num_features].start = HB_FEATURE_GLOBAL_START;
current_features[num_features].end = HB_FEATURE_GLOBAL_END;
current_features[num_features].value = 1; /* enable the feature */
/*
Quote from the HarfBuzz documentation about the `value` attribute:
0 disables the feature, non-zero (usually 1) enables the feature.
For features implemented as lookup type 3 (like 'salt') the value is
a one-based index into the alternates. This algorithm does not
handle these lookup type 3 cases fully.
*/
new_glyphs = hb_set_create();
if ( !hb_set_allocation_successful( new_glyphs ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
feature_tag = HB_SET_VALUE_INVALID;
while ( hb_set_next( feature_tag_pool, &feature_tag ) )
{
hb_set_clear( new_glyphs );
current_features[num_features].tag = feature_tag;
hb_ot_shape_glyphs_closure ( font,
buffer,
current_features,
num_features + 1,
new_glyphs );
if ( !hb_set_allocation_successful( new_glyphs ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
hb_set_subtract( new_glyphs, result );
/* `new_glyphs` now contains all glyphs that appeared in the result */
/* of `hb_ot_shape_glyphs_closure` that haven't already been */
/* accounted for in the result. If this contains any glyphs, we */
/* also need to try this feature in combination with other features */
/* by recursing. */
if ( hb_set_get_population( new_glyphs ) != 0 )
{
/* Remove this feature from the feature pool temporarily so that */
/* a later recursion won't try it. */
hb_set_del( feature_tag_pool, feature_tag );
error = af_all_glyph_variants_helper( font,
buffer,
feature_tag_pool,
current_features,
num_features + 1,
result );
if ( error )
goto Exit;
/* Add back the feature we removed. */
hb_set_add( feature_tag_pool, feature_tag );
if ( !hb_set_allocation_successful( feature_tag_pool ) )
return FT_Err_Out_Of_Memory;
}
}
Exit:
hb_set_destroy( baseline_glyphs );
hb_set_destroy( new_glyphs );
return FT_Err_Ok;
}
static FT_Error
af_all_glyph_variants( FT_Face face,
hb_font_t *hb_font,
FT_UInt32 codepoint,
hb_set_t* result )
{
FT_Error error;
FT_Memory memory = face->memory;
hb_face_t *hb_face = hb_font_get_face( hb_font );
FT_Bool feature_list_done;
unsigned int start_offset;
/* The set of all feature tags in the font. */
hb_set_t *feature_tags = hb_set_create();
hb_set_t *type_3_lookup_indices = hb_set_create();
hb_buffer_t *codepoint_buffer = hb_buffer_create();
hb_codepoint_t *type_3_alternate_glyphs_buffer;
hb_feature_t *feature_buffer;
/* List of features containing type 3 lookups. */
hb_tag_t feature_list[] =
{
HB_TAG( 's', 'a', 'l', 't' ),
HB_TAG( 's', 'w', 's', 'h' ),
HB_TAG( 'n', 'a', 'l', 't' ),
HB_TAG_NONE
};
hb_codepoint_t lookup_index;
FT_UInt base_glyph_index;
if ( !hb_set_allocation_successful( feature_tags ) ||
!hb_buffer_allocation_successful( codepoint_buffer ) ||
!hb_set_allocation_successful ( type_3_lookup_indices ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
/* Populate `feature_tags` using the output of */
/* `hb_ot_layout_table_get_feature_tags`. */
feature_list_done = 0;
start_offset = 0;
while ( !feature_list_done )
{
unsigned int feature_count = 20;
hb_tag_t tags[20];
unsigned int i;
hb_ot_layout_table_get_feature_tags( hb_face,
HB_OT_TAG_GSUB,
start_offset,
&feature_count,
tags );
start_offset += 20;
if ( feature_count < 20 )
feature_list_done = 1;
for ( i = 0; i < feature_count; i++ )
hb_set_add( feature_tags, tags[i] );
if ( !hb_set_allocation_successful( feature_tags ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
}
/* Make a buffer only consisting of the given code point. */
if ( !hb_buffer_pre_allocate( codepoint_buffer, 1 ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
hb_buffer_set_direction( codepoint_buffer, HB_DIRECTION_LTR );
hb_buffer_add( codepoint_buffer, codepoint, 0 );
/* The array of features that is used by the recursive part has at */
/* most as many entries as there are features, so make the length */
/* equal to the length of `feature_tags`. */
if ( ( error = FT_NEW_ARRAY( feature_buffer,
hb_set_get_population( feature_tags ) ) ) )
goto Exit;
error = af_all_glyph_variants_helper( hb_font,
codepoint_buffer,
feature_tags,
feature_buffer,
0,
result );
if ( error )
goto Exit;
/* Add the alternative glyph forms that come from features using
type 3 lookups.
This file from gtk was very useful in figuring out my approach:
https://gitlab.gnome.org/GNOME/gtk/-/blob/40f20fee3d8468749dfb233a6f95921c765c1163/gtk/gtkfontchooserwidget.c#L2100
*/
hb_ot_layout_collect_lookups( hb_face,
HB_OT_TAG_GSUB,
NULL,
NULL,
feature_list,
type_3_lookup_indices );
if ( !hb_set_allocation_successful( type_3_lookup_indices ) )
{
error = FT_Err_Out_Of_Memory;
goto Exit;
}
#define MAX_ALTERNATES 100 /* ad-hoc value */
if ( ( error = FT_NEW_ARRAY( type_3_alternate_glyphs_buffer,
MAX_ALTERNATES ) ) )
goto Exit;
lookup_index = HB_SET_VALUE_INVALID;
base_glyph_index = FT_Get_Char_Index( face, codepoint );
if ( base_glyph_index )
{
while ( hb_set_next( type_3_lookup_indices, &lookup_index ) )
{
unsigned alternate_count = MAX_ALTERNATES;
unsigned i;
hb_ot_layout_lookup_get_glyph_alternates(
hb_face,
lookup_index,
base_glyph_index,
0,
&alternate_count,
type_3_alternate_glyphs_buffer );
for ( i = 0; i < alternate_count; i++ )
hb_set_add( result, type_3_alternate_glyphs_buffer[i] );
}
}
Exit:
hb_set_destroy( feature_tags );
hb_buffer_destroy( codepoint_buffer );
FT_FREE( feature_buffer );
FT_FREE( type_3_alternate_glyphs_buffer );
return error;
}
#endif /*FT_CONFIG_OPTION_USE_HARFBUZZ*/
FT_LOCAL_DEF( FT_Error )
af_reverse_character_map_new( AF_ReverseCharacterMap *map,
AF_FaceGlobals globals )
{
FT_Error error;
FT_Face face = globals->face;
FT_Memory memory = face->memory;
FT_CharMap old_charmap;
FT_Long capacity;
/* Search for a unicode charmap. */
/* If there isn't one, create a blank map. */
FT_TRACE4(( "af_reverse_character_map_new:"
" building reverse character map\n" ));
/* Back up `face->charmap` because `find_unicode_charmap` sets it. */
old_charmap = face->charmap;
if ( ( error = find_unicode_charmap( face ) ) )
goto Exit;
*map = NULL;
if ( FT_NEW( *map ) )
goto Exit;
/* Start with a capacity of 10 entries. */
capacity = 10;
( *map )->length = 0;
if ( FT_NEW_ARRAY( ( *map )->entries, capacity ) )
goto Exit;
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
{
hb_font_t *hb_font = globals->hb_font;
hb_set_t *result_set = hb_set_create();
FT_ULong i;
if ( !hb_set_allocation_successful( result_set ) )
{
error = FT_Err_Out_Of_Memory;
goto harfbuzz_path_Exit;
}
/* Find all glyph variants of the code points, then make an entry */
/* from the glyph to the code point for each one. */
for ( i = 0; i < AF_ADJUSTMENT_DATABASE_LENGTH; i++ )
{
FT_UInt32 codepoint = adjustment_database[i].codepoint;
hb_codepoint_t glyph;
error = af_all_glyph_variants( face,
hb_font,
codepoint,
result_set );
if ( error )
goto harfbuzz_path_Exit;
glyph = HB_SET_VALUE_INVALID;
while ( hb_set_next( result_set, &glyph ) )
{
FT_Long insert_point;
error = af_reverse_character_map_expand( *map, &capacity, memory );
if ( error )
goto harfbuzz_path_Exit;
insert_point = ( *map )->length;
( *map )->length++;
( *map )->entries[insert_point].glyph_index = glyph;
( *map )->entries[insert_point].codepoint = codepoint;
}
hb_set_clear( result_set );
}
harfbuzz_path_Exit:
hb_set_destroy( result_set );
if ( error )
goto Exit;
}
#else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */
{
FT_UInt i;
#ifdef FT_DEBUG_LEVEL_TRACE
int failed_lookups = 0;
#endif
for ( i = 0; i < AF_ADJUSTMENT_DATABASE_LENGTH; i++ )
{
FT_UInt32 codepoint = adjustment_database[i].codepoint;
FT_Int glyph = FT_Get_Char_Index( face, codepoint );
if ( glyph == 0 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
failed_lookups++;
#endif
continue;
}
error = af_reverse_character_map_expand( *map, &capacity, memory );
if ( error )
goto Exit;
( *map )->length++;
( *map )->entries[i].glyph_index = glyph;
( *map )->entries[i].codepoint = codepoint;
}
}
#endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */
ft_qsort( ( *map )->entries,
( *map )->length,
sizeof ( AF_ReverseMapEntry ),
af_reverse_character_map_entry_compare );
FT_TRACE4(( " reverse character map built successfully"
" with %ld entries\n", (*map)->length ));
Exit:
face->charmap = old_charmap;
if ( error )
{
FT_TRACE4(( " error while building reverse character map."
" Using blank map.\n" ));
if ( *map )
FT_FREE( ( *map )->entries );
FT_FREE( *map );
*map = NULL;
return error;
}
return FT_Err_Ok;
}
FT_LOCAL_DEF( FT_Error )
af_reverse_character_map_done( AF_ReverseCharacterMap map,
FT_Memory memory )
{
if ( map )
FT_FREE( map->entries );
FT_FREE( map );
return FT_Err_Ok;
}
/* END */

86
src/autofit/afadjust.h Normal file
View File

@ -0,0 +1,86 @@
/****************************************************************************
*
* afadjust.h
*
* Auto-fitter routines to adjust components based on charcode (header).
*
* Copyright (C) 2023-2024 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* Written by Craig White <gerzytet@gmail.com>.
*
* This file is part of the FreeType project, and may only be used,
* modified, and distributed under the terms of the FreeType project
* license, LICENSE.TXT. By continuing to use, modify, or distribute
* this file you indicate that you have read the license and
* understand and accept it fully.
*
*/
#ifndef AFADJUST_H_
#define AFADJUST_H_
#include <freetype/fttypes.h>
#include "afglobal.h"
#include "aftypes.h"
FT_BEGIN_HEADER
/* The type of adjustment that should be done to prevent cases where */
/* two parts of a character stacked vertically merge, even though they */
/* should be separate. */
typedef enum AF_VerticalSeparationAdjustmentType_
{
/* This means that the hinter should find the topmost contour and push */
/* it up until its lowest point is one pixel above the highest point */
/* not part of that contour. */
AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP,
/* This is the opposite direction. The hinter should find the */
/* bottommost contour and push it down until there is a one-pixel gap. */
AF_VERTICAL_ADJUSTMENT_BOTTOM_CONTOUR_DOWN,
AF_VERTICAL_ADJUSTMENT_NONE
} AF_VerticalSeparationAdjustmentType;
typedef struct AF_AdjustmentDatabaseEntry_
{
FT_UInt32 codepoint;
AF_VerticalSeparationAdjustmentType vertical_separation_adjustment_type;
FT_Bool apply_tilde;
} AF_AdjustmentDatabaseEntry;
FT_LOCAL( AF_VerticalSeparationAdjustmentType )
af_lookup_vertical_separation_type( AF_ReverseCharacterMap map,
FT_Int glyph_index );
FT_LOCAL( FT_Bool )
af_lookup_tilde_correction_type( AF_ReverseCharacterMap map,
FT_Int glyph_index );
/* Allocate and populate the reverse character map, */
/* using the character map within the face. */
FT_LOCAL( FT_Error )
af_reverse_character_map_new( AF_ReverseCharacterMap *map,
AF_FaceGlobals globals );
/* Free the reverse character map. */
FT_LOCAL( FT_Error )
af_reverse_character_map_done( AF_ReverseCharacterMap map,
FT_Memory memory );
FT_END_HEADER
#endif /* AFADJUST_H_ */
/* END */

View File

@ -22,6 +22,7 @@
#include "afglobal.h"
#include "aflatin.h"
#include "aferrors.h"
#include "afadjust.h"
/**************************************************************************
@ -1155,6 +1156,9 @@
af_latin_metrics_check_digits( metrics, face );
}
af_reverse_character_map_new( &metrics->root.reverse_charmap,
metrics->root.globals );
Exit:
face->charmap = oldmap;
return error;
@ -1484,6 +1488,17 @@
}
FT_CALLBACK_DEF( void )
af_latin_metrics_done( AF_StyleMetrics metrics_ )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
af_reverse_character_map_done( metrics->root.reverse_charmap,
metrics->root.globals->face->memory );
}
/* Scale global values in both directions. */
FT_LOCAL_DEF( void )
@ -2737,6 +2752,633 @@
}
#undef FT_COMPONENT
#define FT_COMPONENT afadjust
static void
af_move_contour_vertically( AF_Point contour,
FT_Int movement )
{
AF_Point point = contour;
AF_Point first_point = point;
if ( point )
{
do
{
point->y += movement;
point = point->next;
} while ( point != first_point );
}
}
static FT_Int
af_find_highest_contour( AF_GlyphHints hints )
{
FT_Int highest_contour = -1;
FT_Pos highest_min_y = 0;
FT_Pos current_min_y = 0;
FT_Int contour;
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( !point )
continue;
current_min_y = point->y;
do
{
if ( point->y < current_min_y )
current_min_y = point->y;
point = point->next;
} while ( point != first_point );
if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y;
highest_contour = contour;
}
}
return highest_contour;
}
static void
af_remove_segments_containing_point( AF_GlyphHints hints,
AF_Point point )
{
AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT];
AF_Segment segments = axis->segments;
FT_UInt i;
for ( i = 0; i < axis->num_segments; i++ )
{
AF_Segment seg = &segments[i];
AF_Point p = seg->first;
FT_Bool remove = 0;
while ( 1 )
{
if ( p == point )
{
remove = 1;
break;
}
if ( p == seg->last )
break;
p = p->next;
}
if ( remove )
{
/* First, check the first and last segment of the edge. */
AF_Edge edge = seg->edge;
if ( edge->first == seg && edge->last == seg )
{
/* The edge only consists of the segment to be removed. */
/* Remove the edge. */
*edge = axis->edges[--axis->num_edges];
}
else
{
if ( edge->first == seg )
edge->first = seg->edge_next;
if ( edge->last == seg )
{
edge->last = edge->first;
while ( edge->last->edge_next != seg )
edge->last = edge->last->edge_next;
}
}
/* Now, delete the segment. */
*seg = axis->segments[--axis->num_segments];
i--; /* We have to check the new segment at this position. */
}
}
}
/* Remove all segments containing points on the tilde contour. */
static void
af_latin_remove_tilde_points_from_edges( AF_GlyphHints hints )
{
FT_Int highest_contour = af_find_highest_contour( hints );
AF_Point first_point = hints->contours[highest_contour];
AF_Point p = first_point;
do
{
p = p->next;
af_remove_segments_containing_point( hints, p );
} while ( p != first_point );
}
/*
The tilde-unflattening algorithm sometimes goes too far and makes an
unusually high tilde, where decreasing the ppem will increase the height
instead of a steady decrease in height as less pixels are used.
For example, the 'n tilde' glyph from 'Times New Roman', with forced
autofitting on, exhibits this behaviour in the font size range 16.5 to
18 ppem.
*/
static void
af_latin_stretch_tildes( AF_GlyphHints hints )
{
FT_Int highest_contour = af_find_highest_contour( hints );
AF_Point p = hints->contours[highest_contour];
AF_Point first_point = p;
FT_Pos min_y, max_y;
FT_Short min_fy, max_fy;
FT_Pos min_measurement;
FT_Pos height;
FT_Pos target_height;
min_y = max_y = p->y;
min_fy = max_fy = p->fy;
do
{
p = p->next;
if ( p->y < min_y )
min_y = p->y;
if ( p->y > max_y )
max_y = p->y;
if ( p->fy < min_fy )
min_fy = p->fy;
if ( p->fy > max_fy )
max_fy = p->fy;
} while ( p != first_point );
min_measurement = 32000;
do
{
p = p->next;
if ( !( p->flags & AF_FLAG_CONTROL ) &&
p->prev->y == p->y && p->next->y == p->y &&
p->y != min_y && p->y != max_y &&
p->prev->flags & AF_FLAG_CONTROL &&
p->next->flags & AF_FLAG_CONTROL )
{
/* This point could be a candidate. Find the next and previous */
/* on-curve points, and make sure they are both either above or */
/* below the point, then make the measurement. */
AF_Point prevOn = p->prev;
AF_Point nextOn = p->next;
FT_Pos measurement;
while ( prevOn->flags & AF_FLAG_CONTROL )
prevOn = prevOn->prev;
while ( nextOn->flags & AF_FLAG_CONTROL )
nextOn = nextOn->next;
if ( nextOn->y > p->y && prevOn->y > p->y )
measurement = p->y - min_y;
else if ( nextOn->y < p->y && prevOn->y < p->y )
measurement = max_y - p->y;
else
continue;
if ( measurement < min_measurement )
min_measurement = measurement;
}
} while ( p != first_point );
height = max_y - min_y;
if ( height < 256 ) {
/* touch all points */
p = first_point;
do
{
p = p->next;
if ( !( p->flags & AF_FLAG_CONTROL ) )
p->flags |= AF_FLAG_TOUCH_Y;
} while ( p != first_point );
}
target_height = min_measurement + 64;
if ( height >= target_height )
return;
p = first_point;
do
{
p = p->next;
p->y = ( ( p->y - min_y ) * target_height / height ) + min_y;
p->fy = ( ( p->fy - min_fy ) * target_height / height ) + min_fy;
p->oy = p->y;
} while ( p != first_point );
}
/*
As part of `af_latin_stretch_tildes`, all points in the tilde are marked
as touched, so the existing grid fitting will leave the tilde misaligned
with the grid.
This function moves the tilde contour down to be grid-fitted. We assume
that if moving the tilde down would cause it to touch or overlap another
countour, the vertical adjustment step will fix it.
Because the vertical adjustment step comes after all other grid-fitting
steps, the top edge of the contour under the tilde is usually aligned
with a horizontal grid line. The vertical gap enforced by the vertical
adjustment is exactly one pixel, so if the top edge of the contour below
the tilde is on a grid line, the resulting tilde contour will also be
grid-aligned.
But in cases where the gap is already big enough so that the vertical
adjustment does nothing, this function ensures that even without the
intervention of the vertical adjustment step, the tilde will be
grid-aligned.
*/
static void
af_latin_align_tildes( AF_GlyphHints hints )
{
FT_Int highest_contour = af_find_highest_contour( hints );
AF_Point p = hints->contours[highest_contour];
AF_Point first_point = p;
FT_Pos min_y, max_y;
FT_Pos delta;
do
{
p = p->next;
if ( p->y < min_y )
min_y = p->y;
if ( p->y > max_y )
max_y = p->y;
} while ( p != first_point );
// If the tilde is less than 2 pixels tall, snap the center of it to
// the grid instead of the bottom to improve readability
//
FT_Pos min_y_rounded = FT_PIX_ROUND( min_y );
delta = min_y_rounded - min_y;
FT_Pos height = max_y - min_y;
if ( height < 64*3 ) {
delta += ( FT_PIX_ROUND( height ) - height ) / 2;
}
p = first_point;
do
{
p = p->next;
p->y += delta;
} while ( p != first_point );
}
/* Return 1 if the given contour overlaps horizontally with the bounding */
/* box of all other contours combined. This is a helper for function */
/* `af_glyph_hints_apply_vertical_separation_adjustments`. */
static FT_Bool
af_check_contour_horizontal_overlap( AF_GlyphHints hints,
FT_Int contour_index )
{
FT_Pos contour_max_x = -32000;
FT_Pos contour_min_x = 32000;
FT_Pos others_max_x = -32000;
FT_Pos others_min_x = 32000;
FT_Int contour;
FT_Bool horizontal_overlap;
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point first_point = hints->contours[contour];
AF_Point p = first_point;
do
{
p = p->next;
if ( contour == contour_index )
{
if ( p->x < contour_min_x )
contour_min_x = p->x;
if ( p->x > contour_max_x )
contour_max_x = p->x;
}
else
{
if ( p->x < others_min_x )
others_min_x = p->x;
if ( p->x > others_max_x )
others_max_x = p->x;
}
} while ( p != first_point );
}
horizontal_overlap =
( others_min_x <= contour_max_x && contour_max_x <= others_max_x ) ||
( others_min_x <= contour_min_x && contour_min_x <= others_max_x ) ||
( contour_max_x >= others_max_x && contour_min_x <= others_min_x );
return horizontal_overlap;
}
static void
af_glyph_hints_apply_vertical_separation_adjustments(
AF_GlyphHints hints,
AF_Dimension dim,
FT_Int glyph_index,
AF_ReverseCharacterMap reverse_charmap )
{
FT_TRACE4(( "Entering"
" af_glyph_hints_apply_vertical_separation_adjustments\n" ));
if ( dim != AF_DIMENSION_VERT )
return;
if ( af_lookup_vertical_separation_type( reverse_charmap,
glyph_index ) ==
AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP &&
hints->num_contours >= 2 )
{
FT_Int highest_contour = -1;
FT_Pos highest_min_y = 0;
FT_Pos current_min_y = 0;
FT_Int contour;
FT_Bool horizontal_overlap;
FT_Int adjustment_amount;
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
" Applying vertical adjustment:"
" AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP\n" ));
/* Figure out which contour is the higher one by finding the one */
/* with the highest minimum y value. */
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( !point )
continue;
current_min_y = point->y;
do
{
if ( point->y < current_min_y )
current_min_y = point->y;
point = point->next;
} while ( point != first_point );
if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y;
highest_contour = contour;
}
}
/* Check for a horizontal overlap between the top contour and the */
/* rest. If there is no overlap, do not adjust. */
horizontal_overlap =
af_check_contour_horizontal_overlap( hints, highest_contour );
if ( !horizontal_overlap )
{
FT_TRACE4(( " Top contour does not horizontally overlap"
" with other contours.\n"
" Skipping adjustment.\n" ));
return;
}
AF_Point point = hints->contours[highest_contour];
AF_Point first_point = point;
FT_Pos highest_max_y = point->y;
do
{
if ( point->y > highest_max_y )
{
highest_max_y = point->y;
}
} while ( point != first_point );
/* If there are any contours that have a maximum y coordinate */
/* greater than or equal to the minimum y coordinate of the */
/* previously found highest contour, bump the high contour up until */
/* the distance is one pixel. */
adjustment_amount = 0;
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point;
AF_Point first_point;
FT_Pos max_y;
if ( contour == highest_contour )
continue;
point = hints->contours[contour];
first_point = point;
if ( !point )
continue;
max_y = point->y;
do
{
if ( point->y > max_y )
max_y = point->y;
point = point->next;
} while ( point != first_point );
if ( max_y >= highest_min_y - 64 )
{
adjustment_amount = 64 - ( highest_min_y - max_y );
}
}
FT_TRACE4(( " Calculated adjustment amount 1: %d\n",
adjustment_amount ));
if ( adjustment_amount > 0 && ( highest_max_y - highest_min_y ) < 128 )
{
adjustment_amount += ( 128 - ( highest_max_y - highest_min_y ) ) / 2;
FT_TRACE4(( " Additional push: %d\n",
( 128 - ( highest_max_y - highest_min_y ) ) / 2 ));
}
FT_TRACE4(( " Calculated adjustment amount: %d\n",
adjustment_amount ));
if ( adjustment_amount > 64 )
FT_TRACE4(( " Calculated adjustment amount %d"
" was more than threshold of 64. Not adjusting\n",
adjustment_amount ));
else if ( adjustment_amount > 0 )
{
FT_TRACE4(( " Pushing top contour %d units up\n",
adjustment_amount ));
af_move_contour_vertically( hints->contours[highest_contour],
adjustment_amount );
}
}
else if ( af_lookup_vertical_separation_type( reverse_charmap,
glyph_index ) ==
AF_VERTICAL_ADJUSTMENT_BOTTOM_CONTOUR_DOWN &&
hints->num_contours >= 2 )
{
FT_Int lowest_contour = -1;
FT_Pos lowest_max_y = 0;
FT_Pos current_max_y = 0;
FT_Int contour;
FT_Int adjustment_amount;
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
" Applying vertical adjustment:"
" AF_VERTICAL_ADJUSTMENT_BOTTOM_CONTOUR_DOWN\n" ));
/* Find lowest contour. */
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( !point )
continue;
current_max_y = point->y;
do
{
if ( point->y > current_max_y )
current_max_y = point->y;
point = point->next;
} while ( point != first_point );
if ( lowest_contour == -1 || current_max_y < lowest_max_y )
{
lowest_max_y = current_max_y;
lowest_contour = contour;
}
}
adjustment_amount = 0;
for ( contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point;
AF_Point first_point;
FT_Pos min_y;
if ( contour == lowest_contour )
continue;
point = hints->contours[contour];
first_point = point;
if ( !point )
continue;
min_y = point->y;
do
{
if ( point->y < min_y )
min_y = point->y;
point = point->next;
} while ( point != first_point );
if ( min_y <= lowest_max_y - 64 )
adjustment_amount = 64 - ( min_y - lowest_max_y );
}
if ( adjustment_amount > 64 )
FT_TRACE4(( " Calculated adjustment amount %d"
" was more than threshold of 64. Not adjusting\n",
adjustment_amount ));
else if ( adjustment_amount > 0 )
{
FT_TRACE4(( " Pushing bottom contour %d units down\n",
adjustment_amount ));
af_move_contour_vertically( hints->contours[lowest_contour],
-adjustment_amount );
}
}
else
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
" No vertical adjustment needed\n" ));
FT_TRACE4(( "Exiting"
" af_glyph_hints_apply_vertical_separation_adjustments\n" ));
}
#undef FT_COMPONENT
#define FT_COMPONENT aflatin
/* Compute the snapped width of a given stem, ignoring very thin ones. */
/* There is a lot of voodoo in this function; changing the hard-coded */
/* parameters influence the whole hinting process. */
@ -3547,6 +4189,48 @@
}
#ifdef FT_DEBUG_LEVEL_TRACE
/* Print the height of the topmost contour for debugging purposes. */
/* TODO: remove this once the tilde unflattening works. */
static void
af_latin_trace_height( FT_UInt num,
AF_GlyphHints hints )
{
AF_Point p = hints->contours[af_find_highest_contour(hints)];
AF_Point first_point = p;
FT_Pos min_y, max_y;
min_y = max_y = p->y;
do
{
p = p->next;
if ( !(p->flags & AF_FLAG_CONTROL) )
{
if ( p->y < min_y )
min_y = p->y;
if ( p->y > max_y )
max_y = p->y;
}
} while ( p != first_point );
FT_TRACE4(( "height %d: %ld\n", num, max_y - min_y ));
FT_TRACE4(( "min y %d: %ld\n", num, min_y ));
}
#else
static void
af_latin_trace_height( FT_UInt num,
AF_GlyphHints hints )
{
FT_UNUSED( num );
FT_UNUSED( hints );
}
#endif
/* Apply the complete hinting algorithm to a latin glyph. */
static FT_Error
@ -3581,11 +4265,27 @@
if ( AF_HINTS_DO_VERTICAL( hints ) )
{
FT_Bool is_tilde = af_lookup_tilde_correction_type(
metrics->root.reverse_charmap, glyph_index );
if ( is_tilde )
{
af_latin_trace_height(0, hints );
af_latin_stretch_tildes( hints );
af_latin_trace_height(33, hints );
af_latin_align_tildes( hints );
af_latin_trace_height(1, hints );
}
axis = &metrics->axis[AF_DIMENSION_VERT];
error = af_latin_hints_detect_features( hints,
axis->width_count,
axis->widths,
AF_DIMENSION_VERT );
if ( is_tilde )
af_latin_remove_tilde_points_from_edges( hints );
if ( error )
goto Exit;
@ -3601,9 +4301,19 @@
( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) ) )
{
af_latin_hint_edges( hints, (AF_Dimension)dim );
af_latin_trace_height(2, hints );
af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim );
af_latin_trace_height(3, hints );
af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
af_latin_trace_height(4, hints );
af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
af_latin_trace_height(5, hints );
af_glyph_hints_apply_vertical_separation_adjustments(
hints,
(AF_Dimension)dim,
glyph_index,
metrics->root.reverse_charmap );
af_latin_trace_height(99, hints );
}
}
@ -3632,7 +4342,7 @@
(AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init, /* style_metrics_init */
(AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale, /* style_metrics_scale */
(AF_WritingSystem_DoneMetricsFunc) NULL, /* style_metrics_done */
(AF_WritingSystem_DoneMetricsFunc) af_latin_metrics_done, /* style_metrics_done */
(AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */
(AF_WritingSystem_InitHintsFunc) af_latin_hints_init, /* style_hints_init */

View File

@ -406,6 +406,27 @@ extern void* af_debug_hints_;
typedef struct AF_FaceGlobalsRec_* AF_FaceGlobals;
/* Store a mapping from glyphs to unicode codepoints. */
/* See `afadjust.c` for details. */
typedef struct AF_ReverseMapEntry_
{
FT_Int glyph_index;
FT_UInt32 codepoint;
} AF_ReverseMapEntry;
typedef struct AF_ReverseCharacterMapRec_
{
FT_Long length;
AF_ReverseMapEntry *entries;
} AF_ReverseCharacterMapRec;
typedef struct AF_ReverseCharacterMapRec_* AF_ReverseCharacterMap;
/* This is the main structure that combines everything. Autofit modules */
/* specific to writing systems derive their structures from it, for */
/* example `AF_LatinMetrics'. */
@ -418,6 +439,8 @@ extern void* af_debug_hints_;
AF_FaceGlobals globals; /* to access properties */
AF_ReverseCharacterMap reverse_charmap;
} AF_StyleMetricsRec;

View File

@ -19,6 +19,7 @@
#define FT_MAKE_OPTION_SINGLE_OBJECT
#include "ft-hb.c"
#include "afadjust.c"
#include "afblue.c"
#include "afcjk.c"
#include "afdummy.c"

View File

@ -28,7 +28,8 @@ AUTOF_COMPILE := $(CC) $(ANSIFLAGS) \
# AUTOF driver sources (i.e., C files)
#
AUTOF_DRV_SRC := $(AUTOF_DIR)/afblue.c \
AUTOF_DRV_SRC := $(AUTOF_DIR)/afadjust.c \
$(AUTOF_DIR)/afblue.c \
$(AUTOF_DIR)/afcjk.c \
$(AUTOF_DIR)/afdummy.c \
$(AUTOF_DIR)/afglobal.c \

View File

@ -1359,21 +1359,9 @@
}
/**************************************************************************
*
* @Function:
* find_unicode_charmap
*
* @Description:
* This function finds a Unicode charmap, if there is one.
* And if there is more than one, it tries to favour the more
* extensive one, i.e., one that supports UCS-4 against those which
* are limited to the BMP (said UCS-2 encoding.)
*
* This function is called from open_face() (just below), and also
* from FT_Select_Charmap( ..., FT_ENCODING_UNICODE ).
*/
static FT_Error
/* documentation is in ftobjs.h */
FT_BASE_DEF( FT_Error )
find_unicode_charmap( FT_Face face )
{
FT_CharMap* first;