| /***************************************************************************/ |
| /* */ |
| /* ahoptim.c */ |
| /* */ |
| /* FreeType auto hinting outline optimization (body). */ |
| /* */ |
| /* Copyright 2000-2001 Catharon Productions Inc. */ |
| /* Author: David Turner */ |
| /* */ |
| /* This file is part of the Catharon Typography Project and shall only */ |
| /* be used, modified, and distributed under the terms of the Catharon */ |
| /* Open Source License that should come with this file under the name */ |
| /* `CatharonLicense.txt'. By continuing to use, modify, or distribute */ |
| /* this file you indicate that you have read the license and */ |
| /* understand and accept it fully. */ |
| /* */ |
| /* Note that this license is compatible with the FreeType license. */ |
| /* */ |
| /***************************************************************************/ |
| |
| |
| /*************************************************************************/ |
| /* */ |
| /* This module is in charge of optimising the outlines produced by the */ |
| /* auto-hinter in direct mode. This is required at small pixel sizes in */ |
| /* order to ensure coherent spacing, among other things.. */ |
| /* */ |
| /* The technique used in this module is a simplified simulated */ |
| /* annealing. */ |
| /* */ |
| /*************************************************************************/ |
| |
| |
| #include <ft2build.h> |
| #include FT_INTERNAL_OBJECTS_H /* for ALLOC_ARRAY() and FREE() */ |
| #include "ahoptim.h" |
| |
| |
| /* define this macro to use brute force optimisation -- this is slow, */ |
| /* but a good way to perfect the distortion function `by hand' through */ |
| /* tweaking */ |
| #define AH_BRUTE_FORCE |
| |
| |
| #define xxxAH_DEBUG_OPTIM |
| |
| |
| #undef LOG |
| #ifdef AH_DEBUG_OPTIM |
| |
| #define LOG( x ) optim_log ## x |
| |
| #else |
| |
| #define LOG( x ) |
| |
| #endif /* AH_DEBUG_OPTIM */ |
| |
| |
| #ifdef AH_DEBUG_OPTIM |
| |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #define FLOAT( x ) ( (float)( (x) / 64.0 ) ) |
| |
| static void |
| optim_log( const char* fmt, ... ) |
| { |
| va_list ap; |
| |
| |
| va_start( ap, fmt ); |
| vprintf( fmt, ap ); |
| va_end( ap ); |
| } |
| |
| |
| static void |
| AH_Dump_Stems( AH_Optimizer* optimizer ) |
| { |
| int n; |
| AH_Stem* stem; |
| |
| |
| stem = optimizer->stems; |
| for ( n = 0; n < optimizer->num_stems; n++, stem++ ) |
| { |
| LOG(( " %c%2d [%.1f:%.1f]={%.1f:%.1f}=" |
| "<%1.f..%1.f> force=%.1f speed=%.1f\n", |
| optimizer->vertical ? 'V' : 'H', n, |
| FLOAT( stem->edge1->opos ), FLOAT( stem->edge2->opos ), |
| FLOAT( stem->edge1->pos ), FLOAT( stem->edge2->pos ), |
| FLOAT( stem->min_pos ), FLOAT( stem->max_pos ), |
| FLOAT( stem->force ), FLOAT( stem->velocity ) )); |
| } |
| } |
| |
| |
| static void |
| AH_Dump_Stems2( AH_Optimizer* optimizer ) |
| { |
| int n; |
| AH_Stem* stem; |
| |
| |
| stem = optimizer->stems; |
| for ( n = 0; n < optimizer->num_stems; n++, stem++ ) |
| { |
| LOG(( " %c%2d [%.1f]=<%1.f..%1.f> force=%.1f speed=%.1f\n", |
| optimizer->vertical ? 'V' : 'H', n, |
| FLOAT( stem->pos ), |
| FLOAT( stem->min_pos ), FLOAT( stem->max_pos ), |
| FLOAT( stem->force ), FLOAT( stem->velocity ) )); |
| } |
| } |
| |
| |
| static void |
| AH_Dump_Springs( AH_Optimizer* optimizer ) |
| { |
| int n; |
| AH_Spring* spring; |
| AH_Stem* stems; |
| |
| |
| spring = optimizer->springs; |
| stems = optimizer->stems; |
| LOG(( "%cSprings ", optimizer->vertical ? 'V' : 'H' )); |
| |
| for ( n = 0; n < optimizer->num_springs; n++, spring++ ) |
| { |
| LOG(( " [%d-%d:%.1f:%1.f:%.1f]", |
| spring->stem1 - stems, spring->stem2 - stems, |
| FLOAT( spring->owidth ), |
| FLOAT( spring->stem2->pos - |
| ( spring->stem1->pos + spring->stem1->width ) ), |
| FLOAT( spring->tension ) )); |
| } |
| |
| LOG(( "\n" )); |
| } |
| |
| #endif /* AH_DEBUG_OPTIM */ |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /**** ****/ |
| /**** COMPUTE STEMS AND SPRINGS IN AN OUTLINE ****/ |
| /**** ****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| |
| static int |
| valid_stem_segments( AH_Segment* seg1, |
| AH_Segment* seg2 ) |
| { |
| return seg1->serif == 0 && |
| seg2 && |
| seg2->link == seg1 && |
| seg1->pos < seg2->pos && |
| seg1->min_coord <= seg2->max_coord && |
| seg2->min_coord <= seg1->max_coord; |
| } |
| |
| |
| /* compute all stems in an outline */ |
| static int |
| optim_compute_stems( AH_Optimizer* optimizer ) |
| { |
| AH_Outline* outline = optimizer->outline; |
| FT_Fixed scale; |
| FT_Memory memory = optimizer->memory; |
| FT_Error error = 0; |
| FT_Int dimension; |
| AH_Edge* edges; |
| AH_Edge* edge_limit; |
| AH_Stem** p_stems; |
| FT_Int* p_num_stems; |
| |
| |
| edges = outline->horz_edges; |
| edge_limit = edges + outline->num_hedges; |
| scale = outline->y_scale; |
| |
| p_stems = &optimizer->horz_stems; |
| p_num_stems = &optimizer->num_hstems; |
| |
| for ( dimension = 1; dimension >= 0; dimension-- ) |
| { |
| AH_Stem* stems = 0; |
| FT_Int num_stems = 0; |
| AH_Edge* edge; |
| |
| |
| /* first of all, count the number of stems in this direction */ |
| for ( edge = edges; edge < edge_limit; edge++ ) |
| { |
| AH_Segment* seg = edge->first; |
| |
| |
| do |
| { |
| if (valid_stem_segments( seg, seg->link ) ) |
| num_stems++; |
| |
| seg = seg->edge_next; |
| |
| } while ( seg != edge->first ); |
| } |
| |
| /* now allocate the stems and build their table */ |
| if ( num_stems > 0 ) |
| { |
| AH_Stem* stem; |
| |
| |
| if ( ALLOC_ARRAY( stems, num_stems, AH_Stem ) ) |
| goto Exit; |
| |
| stem = stems; |
| for ( edge = edges; edge < edge_limit; edge++ ) |
| { |
| AH_Segment* seg = edge->first; |
| AH_Segment* seg2; |
| |
| |
| do |
| { |
| seg2 = seg->link; |
| if ( valid_stem_segments( seg, seg2 ) ) |
| { |
| AH_Edge* edge1 = seg->edge; |
| AH_Edge* edge2 = seg2->edge; |
| |
| |
| stem->edge1 = edge1; |
| stem->edge2 = edge2; |
| stem->opos = edge1->opos; |
| stem->pos = edge1->pos; |
| stem->owidth = edge2->opos - edge1->opos; |
| stem->width = edge2->pos - edge1->pos; |
| |
| /* compute min_coord and max_coord */ |
| { |
| FT_Pos min_coord = seg->min_coord; |
| FT_Pos max_coord = seg->max_coord; |
| |
| |
| if ( seg2->min_coord > min_coord ) |
| min_coord = seg2->min_coord; |
| |
| if ( seg2->max_coord < max_coord ) |
| max_coord = seg2->max_coord; |
| |
| stem->min_coord = min_coord; |
| stem->max_coord = max_coord; |
| } |
| |
| /* compute minimum and maximum positions for stem -- */ |
| /* note that the left-most/bottom-most stem has always */ |
| /* a fixed position */ |
| if ( stem == stems || edge1->blue_edge || edge2->blue_edge ) |
| { |
| /* this stem cannot move; it is snapped to a blue edge */ |
| stem->min_pos = stem->pos; |
| stem->max_pos = stem->pos; |
| } |
| else |
| { |
| /* this edge can move; compute its min and max positions */ |
| FT_Pos pos1 = stem->opos; |
| FT_Pos pos2 = pos1 + stem->owidth - stem->width; |
| FT_Pos min1 = pos1 & -64; |
| FT_Pos min2 = pos2 & -64; |
| |
| |
| stem->min_pos = min1; |
| stem->max_pos = min1 + 64; |
| if ( min2 < min1 ) |
| stem->min_pos = min2; |
| else |
| stem->max_pos = min2 + 64; |
| |
| /* XXX: just to see what it does */ |
| stem->max_pos += 64; |
| |
| /* just for the case where direct hinting did some */ |
| /* incredible things (e.g. blue edge shifts) */ |
| if ( stem->min_pos > stem->pos ) |
| stem->min_pos = stem->pos; |
| |
| if ( stem->max_pos < stem->pos ) |
| stem->max_pos = stem->pos; |
| } |
| |
| stem->velocity = 0; |
| stem->force = 0; |
| |
| stem++; |
| } |
| seg = seg->edge_next; |
| |
| } while ( seg != edge->first ); |
| } |
| } |
| |
| *p_stems = stems; |
| *p_num_stems = num_stems; |
| |
| edges = outline->vert_edges; |
| edge_limit = edges + outline->num_vedges; |
| scale = outline->x_scale; |
| |
| p_stems = &optimizer->vert_stems; |
| p_num_stems = &optimizer->num_vstems; |
| } |
| |
| Exit: |
| |
| #ifdef AH_DEBUG_OPTIM |
| AH_Dump_Stems( optimizer ); |
| #endif |
| |
| return error; |
| } |
| |
| |
| /* returns the spring area between two stems, 0 if none */ |
| static FT_Pos |
| stem_spring_area( AH_Stem* stem1, |
| AH_Stem* stem2 ) |
| { |
| FT_Pos area1 = stem1->max_coord - stem1->min_coord; |
| FT_Pos area2 = stem2->max_coord - stem2->min_coord; |
| FT_Pos min = stem1->min_coord; |
| FT_Pos max = stem1->max_coord; |
| FT_Pos area; |
| |
| |
| /* order stems */ |
| if ( stem2->opos <= stem1->opos + stem1->owidth ) |
| return 0; |
| |
| if ( min < stem2->min_coord ) |
| min = stem2->min_coord; |
| |
| if ( max < stem2->max_coord ) |
| max = stem2->max_coord; |
| |
| area = ( max-min ); |
| if ( 2 * area < area1 && 2 * area < area2 ) |
| area = 0; |
| |
| return area; |
| } |
| |
| |
| /* compute all springs in an outline */ |
| static int |
| optim_compute_springs( AH_Optimizer* optimizer ) |
| { |
| /* basically, a spring exists between two stems if most of their */ |
| /* surface is aligned */ |
| FT_Memory memory = optimizer->memory; |
| |
| AH_Stem* stems; |
| AH_Stem* stem_limit; |
| AH_Stem* stem; |
| int dimension; |
| int error = 0; |
| |
| FT_Int* p_num_springs; |
| AH_Spring** p_springs; |
| |
| |
| stems = optimizer->horz_stems; |
| stem_limit = stems + optimizer->num_hstems; |
| |
| p_springs = &optimizer->horz_springs; |
| p_num_springs = &optimizer->num_hsprings; |
| |
| for ( dimension = 1; dimension >= 0; dimension-- ) |
| { |
| FT_Int num_springs = 0; |
| AH_Spring* springs = 0; |
| |
| |
| /* first of all, count stem springs */ |
| for ( stem = stems; stem + 1 < stem_limit; stem++ ) |
| { |
| AH_Stem* stem2; |
| |
| |
| for ( stem2 = stem+1; stem2 < stem_limit; stem2++ ) |
| if ( stem_spring_area( stem, stem2 ) ) |
| num_springs++; |
| } |
| |
| /* then allocate and build the springs table */ |
| if ( num_springs > 0 ) |
| { |
| AH_Spring* spring; |
| |
| |
| /* allocate table of springs */ |
| if ( ALLOC_ARRAY( springs, num_springs, AH_Spring ) ) |
| goto Exit; |
| |
| /* fill the springs table */ |
| spring = springs; |
| for ( stem = stems; stem+1 < stem_limit; stem++ ) |
| { |
| AH_Stem* stem2; |
| FT_Pos area; |
| |
| |
| for ( stem2 = stem + 1; stem2 < stem_limit; stem2++ ) |
| { |
| area = stem_spring_area( stem, stem2 ); |
| if ( area ) |
| { |
| /* add a new spring here */ |
| spring->stem1 = stem; |
| spring->stem2 = stem2; |
| spring->owidth = stem2->opos - ( stem->opos + stem->owidth ); |
| spring->tension = 0; |
| |
| spring++; |
| } |
| } |
| } |
| } |
| *p_num_springs = num_springs; |
| *p_springs = springs; |
| |
| stems = optimizer->vert_stems; |
| stem_limit = stems + optimizer->num_vstems; |
| |
| p_springs = &optimizer->vert_springs; |
| p_num_springs = &optimizer->num_vsprings; |
| } |
| |
| Exit: |
| |
| #ifdef AH_DEBUG_OPTIM |
| AH_Dump_Springs( optimizer ); |
| #endif |
| |
| return error; |
| } |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /**** ****/ |
| /**** OPTIMIZE THROUGH MY STRANGE SIMULATED ANNEALING ALGO ;-) ****/ |
| /**** ****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| #ifndef AH_BRUTE_FORCE |
| |
| /* compute all spring tensions */ |
| static void |
| optim_compute_tensions( AH_Optimizer* optimizer ) |
| { |
| AH_Spring* spring = optimizer->springs; |
| AH_Spring* limit = spring + optimizer->num_springs; |
| |
| |
| for ( ; spring < limit; spring++ ) |
| { |
| AH_Stem* stem1 = spring->stem1; |
| AH_Stem* stem2 = spring->stem2; |
| FT_Int status; |
| |
| FT_Pos width; |
| FT_Pos tension; |
| FT_Pos sign; |
| |
| |
| /* compute the tension; it simply is -K*(new_width-old_width) */ |
| width = stem2->pos - ( stem1->pos + stem1->width ); |
| tension = width - spring->owidth; |
| |
| sign = 1; |
| if ( tension < 0 ) |
| { |
| sign = -1; |
| tension = -tension; |
| } |
| |
| if ( width <= 0 ) |
| tension = 32000; |
| else |
| tension = ( tension << 10 ) / width; |
| |
| tension = -sign * FT_MulFix( tension, optimizer->tension_scale ); |
| spring->tension = tension; |
| |
| /* now, distribute tension among the englobing stems, if they */ |
| /* are able to move */ |
| status = 0; |
| if ( stem1->pos <= stem1->min_pos ) |
| status |= 1; |
| if ( stem2->pos >= stem2->max_pos ) |
| status |= 2; |
| |
| if ( !status ) |
| tension /= 2; |
| |
| if ( ( status & 1 ) == 0 ) |
| stem1->force -= tension; |
| |
| if ( ( status & 2 ) == 0 ) |
| stem2->force += tension; |
| } |
| } |
| |
| |
| /* compute all stem movements -- returns 0 if nothing moved */ |
| static int |
| optim_compute_stem_movements( AH_Optimizer* optimizer ) |
| { |
| AH_Stem* stems = optimizer->stems; |
| AH_Stem* limit = stems + optimizer->num_stems; |
| AH_Stem* stem = stems; |
| int moved = 0; |
| |
| |
| /* set initial forces to velocity */ |
| for ( stem = stems; stem < limit; stem++ ) |
| { |
| stem->force = stem->velocity; |
| stem->velocity /= 2; /* XXX: Heuristics */ |
| } |
| |
| /* compute the sum of forces applied on each stem */ |
| optim_compute_tensions( optimizer ); |
| |
| #ifdef AH_DEBUG_OPTIM |
| AH_Dump_Springs( optimizer ); |
| AH_Dump_Stems2( optimizer ); |
| #endif |
| |
| /* now, see whether something can move */ |
| for ( stem = stems; stem < limit; stem++ ) |
| { |
| if ( stem->force > optimizer->tension_threshold ) |
| { |
| /* there is enough tension to move the stem to the right */ |
| if ( stem->pos < stem->max_pos ) |
| { |
| stem->pos += 64; |
| stem->velocity = stem->force / 2; |
| moved = 1; |
| } |
| else |
| stem->velocity = 0; |
| } |
| else if ( stem->force < optimizer->tension_threshold ) |
| { |
| /* there is enough tension to move the stem to the left */ |
| if ( stem->pos > stem->min_pos ) |
| { |
| stem->pos -= 64; |
| stem->velocity = stem->force / 2; |
| moved = 1; |
| } |
| else |
| stem->velocity = 0; |
| } |
| } |
| |
| /* return 0 if nothing moved */ |
| return moved; |
| } |
| |
| #endif /* AH_BRUTE_FORCE */ |
| |
| |
| /* compute current global distortion from springs */ |
| static FT_Pos |
| optim_compute_distortion( AH_Optimizer* optimizer ) |
| { |
| AH_Spring* spring = optimizer->springs; |
| AH_Spring* limit = spring + optimizer->num_springs; |
| FT_Pos distortion = 0; |
| |
| |
| for ( ; spring < limit; spring++ ) |
| { |
| AH_Stem* stem1 = spring->stem1; |
| AH_Stem* stem2 = spring->stem2; |
| FT_Pos width; |
| |
| width = stem2->pos - ( stem1->pos + stem1->width ); |
| width -= spring->owidth; |
| if ( width < 0 ) |
| width = -width; |
| |
| distortion += width; |
| } |
| |
| return distortion; |
| } |
| |
| |
| /* record stems configuration in `best of' history */ |
| static void |
| optim_record_configuration( AH_Optimizer* optimizer ) |
| { |
| FT_Pos distortion; |
| AH_Configuration* configs = optimizer->configs; |
| AH_Configuration* limit = configs + optimizer->num_configs; |
| AH_Configuration* config; |
| |
| |
| distortion = optim_compute_distortion( optimizer ); |
| LOG(( "config distortion = %.1f ", FLOAT( distortion * 64 ) )); |
| |
| /* check that we really need to add this configuration to our */ |
| /* sorted history */ |
| if ( limit > configs && limit[-1].distortion < distortion ) |
| { |
| LOG(( "ejected\n" )); |
| return; |
| } |
| |
| /* add new configuration at the end of the table */ |
| { |
| int n; |
| |
| |
| config = limit; |
| if ( optimizer->num_configs < AH_MAX_CONFIGS ) |
| optimizer->num_configs++; |
| else |
| config--; |
| |
| config->distortion = distortion; |
| |
| for ( n = 0; n < optimizer->num_stems; n++ ) |
| config->positions[n] = optimizer->stems[n].pos; |
| } |
| |
| /* move the current configuration towards the front of the list */ |
| /* when necessary -- yes this is slow bubble sort ;-) */ |
| while ( config > configs && config[0].distortion < config[-1].distortion ) |
| { |
| AH_Configuration temp; |
| |
| |
| config--; |
| temp = config[0]; |
| config[0] = config[1]; |
| config[1] = temp; |
| } |
| LOG(( "recorded!\n" )); |
| } |
| |
| |
| #ifdef AH_BRUTE_FORCE |
| |
| /* optimize outline in a single direction */ |
| static void |
| optim_compute( AH_Optimizer* optimizer ) |
| { |
| int n; |
| FT_Bool moved; |
| |
| AH_Stem* stem = optimizer->stems; |
| AH_Stem* limit = stem + optimizer->num_stems; |
| |
| |
| /* empty, exit */ |
| if ( stem >= limit ) |
| return; |
| |
| optimizer->num_configs = 0; |
| |
| stem = optimizer->stems; |
| for ( ; stem < limit; stem++ ) |
| stem->pos = stem->min_pos; |
| |
| do |
| { |
| /* record current configuration */ |
| optim_record_configuration( optimizer ); |
| |
| /* now change configuration */ |
| moved = 0; |
| for ( stem = optimizer->stems; stem < limit; stem++ ) |
| { |
| if ( stem->pos < stem->max_pos ) |
| { |
| stem->pos += 64; |
| moved = 1; |
| break; |
| } |
| |
| stem->pos = stem->min_pos; |
| } |
| } while ( moved ); |
| |
| /* now, set the best stem positions */ |
| for ( n = 0; n < optimizer->num_stems; n++ ) |
| { |
| AH_Stem* stem = optimizer->stems + n; |
| FT_Pos pos = optimizer->configs[0].positions[n]; |
| |
| |
| stem->edge1->pos = pos; |
| stem->edge2->pos = pos + stem->width; |
| |
| stem->edge1->flags |= ah_edge_done; |
| stem->edge2->flags |= ah_edge_done; |
| } |
| } |
| |
| #else /* AH_BRUTE_FORCE */ |
| |
| /* optimize outline in a single direction */ |
| static void |
| optim_compute( AH_Optimizer* optimizer ) |
| { |
| int n, counter, counter2; |
| |
| |
| optimizer->num_configs = 0; |
| optimizer->tension_scale = 0x80000L; |
| optimizer->tension_threshold = 64; |
| |
| /* record initial configuration threshold */ |
| optim_record_configuration( optimizer ); |
| |
| counter = 0; |
| for ( counter2 = optimizer->num_stems*8; counter2 >= 0; counter2-- ) |
| { |
| if ( counter == 0 ) |
| counter = 2 * optimizer->num_stems; |
| |
| if ( !optim_compute_stem_movements( optimizer ) ) |
| break; |
| |
| optim_record_configuration( optimizer ); |
| |
| counter--; |
| if ( counter == 0 ) |
| optimizer->tension_scale /= 2; |
| } |
| |
| /* now, set the best stem positions */ |
| for ( n = 0; n < optimizer->num_stems; n++ ) |
| { |
| AH_Stem* stem = optimizer->stems + n; |
| FT_Pos pos = optimizer->configs[0].positions[n]; |
| |
| |
| stem->edge1->pos = pos; |
| stem->edge2->pos = pos + stem->width; |
| |
| stem->edge1->flags |= ah_edge_done; |
| stem->edge2->flags |= ah_edge_done; |
| } |
| } |
| |
| #endif /* AH_BRUTE_FORCE */ |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /**** ****/ |
| /**** HIGH-LEVEL OPTIMIZER API ****/ |
| /**** ****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| |
| /* releases the optimization data */ |
| void |
| AH_Optimizer_Done( AH_Optimizer* optimizer ) |
| { |
| if ( optimizer ) |
| { |
| FT_Memory memory = optimizer->memory; |
| |
| |
| FREE( optimizer->horz_stems ); |
| FREE( optimizer->vert_stems ); |
| FREE( optimizer->horz_springs ); |
| FREE( optimizer->vert_springs ); |
| FREE( optimizer->positions ); |
| } |
| } |
| |
| |
| /* loads the outline into the optimizer */ |
| int |
| AH_Optimizer_Init( AH_Optimizer* optimizer, |
| AH_Outline* outline, |
| FT_Memory memory ) |
| { |
| FT_Error error; |
| |
| |
| MEM_Set( optimizer, 0, sizeof ( *optimizer ) ); |
| optimizer->outline = outline; |
| optimizer->memory = memory; |
| |
| LOG(( "initializing new optimizer\n" )); |
| /* compute stems and springs */ |
| error = optim_compute_stems ( optimizer ) || |
| optim_compute_springs( optimizer ); |
| if ( error ) |
| goto Fail; |
| |
| /* allocate stem positions history and configurations */ |
| { |
| int n, max_stems; |
| |
| |
| max_stems = optimizer->num_hstems; |
| if ( max_stems < optimizer->num_vstems ) |
| max_stems = optimizer->num_vstems; |
| |
| if ( ALLOC_ARRAY( optimizer->positions, |
| max_stems * AH_MAX_CONFIGS, FT_Pos ) ) |
| goto Fail; |
| |
| optimizer->num_configs = 0; |
| for ( n = 0; n < AH_MAX_CONFIGS; n++ ) |
| optimizer->configs[n].positions = optimizer->positions + |
| n * max_stems; |
| } |
| |
| return error; |
| |
| Fail: |
| AH_Optimizer_Done( optimizer ); |
| return error; |
| } |
| |
| |
| /* compute optimal outline */ |
| void |
| AH_Optimizer_Compute( AH_Optimizer* optimizer ) |
| { |
| optimizer->num_stems = optimizer->num_hstems; |
| optimizer->stems = optimizer->horz_stems; |
| optimizer->num_springs = optimizer->num_hsprings; |
| optimizer->springs = optimizer->horz_springs; |
| |
| if ( optimizer->num_springs > 0 ) |
| { |
| LOG(( "horizontal optimization ------------------------\n" )); |
| optim_compute( optimizer ); |
| } |
| |
| optimizer->num_stems = optimizer->num_vstems; |
| optimizer->stems = optimizer->vert_stems; |
| optimizer->num_springs = optimizer->num_vsprings; |
| optimizer->springs = optimizer->vert_springs; |
| |
| if ( optimizer->num_springs ) |
| { |
| LOG(( "vertical optimization --------------------------\n" )); |
| optim_compute( optimizer ); |
| } |
| } |
| |
| |
| /* END */ |