/*@z17.c:Gap Widths:GetGap()@*************************************************/ /* */ /* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.41) */ /* COPYRIGHT (C) 1991, 2023 Jeffrey H. Kingston */ /* */ /* Jeffrey H. Kingston (jeff@it.usyd.edu.au) */ /* School of Information Technologies */ /* The University of Sydney 2006 */ /* AUSTRALIA */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either Version 3, or (at your option) */ /* any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA */ /* */ /* FILE: z17.c */ /* MODULE: Gap Widths */ /* EXTERNS: GetGap(), MinGap(), ExtraGap(), ActualGap(), EchoGap() */ /* */ /*****************************************************************************/ #include "externs.h" /*****************************************************************************/ /* */ /* int GetWidth(OBJECT x, STYLE *style) */ /* */ /* Get a width from object x, according to the grammar */ /* */ /* ::= */ /* ::= c | i | p | m | f | s | v | y | z */ /* */ /*****************************************************************************/ int GetWidth(OBJECT x, STYLE *style) { int res; float num; FULL_CHAR *str; debug2(DGW, D, "GetWidth(%s, %s)", EchoObject(x), EchoStyle(style)); /* make sure we have a WORD or QWORD argument */ if( !is_word(type(x)) ) { Error(17, 11, "width is not a simple word (replacing with 5c)", WARN, &fpos(x)); debug1(DGW, D, "GetWidth failing (x = %s)", EchoObject(x)); return 5 * CM; } str = string(x); /* ignore initial + or - */ if( *str == '+' || *str == '-' ) { Error(17, 12, "ignoring initial %c character in width", WARN, &fpos(x), *str); str++; } /* if word is empty, error */ if( *str == '\0' ) { Error(17, 13, "width is empty (replacing with 5c)", WARN, &fpos(x)); debug0(DGW, D, "GetWidth failing (null word)"); return 5 * CM; } /* read the width */ if( sscanf((char *) str, "%f", &num) != 1 ) { Error(17, 14, "width missing from %s (replacing with 5c)", WARN, &fpos(x), str); debug0(DGW, D, "GetWidth failing (width missing)"); return 5 * CM; } while( numericchar(*str) ) str++; /* make sure there is a units letter */ if( *str == '\0' ) { Error(17, 15, "unit missing from width %s (5c substituted)", WARN, &fpos(x), string(x)); res = 5 * CM; } /* make sure there is nothing after the units letter */ else if( *(str + 1) != '\0' ) { Error(17, 16, "extra character(s) at end of width %s (5c substituted)", WARN, &fpos(x), string(x)); res = 5 * CM; } /* read the compulsory unit and calculate length */ else switch( *str ) { case CH_UNIT_CM: res = num * CM; break; case CH_UNIT_IN: res = num * IN; break; case CH_UNIT_PT: res = num * PT; break; case CH_UNIT_EM: res = num * EM; break; case CH_UNIT_FT: res = num * FontSize(font(*style), x); break; case CH_UNIT_SP: res = num * width(space_gap(*style)); break; case CH_UNIT_VS: res = num * width(line_gap(*style)); break; case CH_UNIT_YU: res = num * yunit(*style); break; case CH_UNIT_ZU: res = num * zunit(*style); break; case CH_UNIT_WD: case CH_UNIT_BD: case CH_UNIT_RL: case CH_UNIT_DG: Error(17, 17, "'%c' unit not allowed in width (5c substituted)", WARN, &fpos(x), *str); res = 5 * CM; break; default: Error(17, 18, "unknown unit in width %s (5c substituted)", WARN, &fpos(x), string(x)); res = 5 * CM; break; } debug1(DGW, D, "GetWidth returning %d", res); return res; } /*****************************************************************************/ /* */ /* GetGap(x, style, res_gap, res_inc) */ /* */ /* Object x is expected to be a WORD or QWORD containing a gap: */ /* */ /* ::= [ ] [ ] [ ] */ /* ::= */ /* */ /* ::= u */ /* ::= + | - */ /* ::= */ /* ::= c | i | p | m | f | s */ /* ::= v | w | b | r | d | y | z */ /* ::= e | h | x | o | k | t | n */ /* */ /* Set *res_gap to the gap in the strings of x; *res_inc is the increment. */ /* The gap is calculated using the given style. */ /* If the gap is empty, this is a synonym for 0ie. */ /* If there is an error, GetGap prints a message and returns 0ie. */ /* */ /*****************************************************************************/ #define setwidths(x, y) w = x; units(*res_gap) = y; break; void GetGap(OBJECT x, STYLE *style, GAP *res_gap, unsigned *res_inc) { int w; float num; FULL_CHAR *str; debug2(DGW, D, "GetGap( %s, %s, res_gap, res_inc )", EchoObject(x), EchoStyle(style)); nobreak(*res_gap) = FALSE; width(*res_gap) = 0; units(*res_gap) = FIXED_UNIT; mode(*res_gap) = EDGE_MODE; *res_inc = GAP_ABS; /* make sure we have a WORD or QWORD argument */ if( !is_word(type(x)) ) { Error(17, 1, "gap is not a simple word", WARN, &fpos(x)); debug1(DGW, D, "GetGap failing (x = %s)", EchoObject(x)); return; } str = string(x); /* if word is empty, return 0ie */ if( *str == '\0' ) { debug0(DGW, D, "GetGap returning (null word)"); return; } /* read the optional gap increment */ if( *str == CH_INCGAP ) *res_inc = GAP_INC, str++; else if( *str == CH_DECGAP ) *res_inc = GAP_DEC, str++; /* read the gap width */ if( sscanf((char *) str, "%f", &num) != 1 ) { Error(17, 2, "width missing from %s", WARN, &fpos(x), string(x)); Error(17, 3, "%s, %s and %s must be enclosed in double quotes", WARN, &fpos(x), KW_VCAT_NJ, KW_HCAT_NJ, KW_ACAT_NJ); debug0(DGW, D, "GetGap failing (width missing)"); return; } while( numericchar(*str) ) str++; /* read the compulsory units, calculate length, and check reasonableness */ switch( *str++ ) { case CH_UNIT_CM: setwidths( num*CM, FIXED_UNIT ); case CH_UNIT_IN: setwidths( num*IN, FIXED_UNIT ); case CH_UNIT_PT: setwidths( num*PT, FIXED_UNIT ); case CH_UNIT_EM: setwidths( num*EM, FIXED_UNIT ); case CH_UNIT_FT: setwidths( num*FontSize(font(*style), x), FIXED_UNIT ); case CH_UNIT_SP: setwidths( num*width(space_gap(*style)), FIXED_UNIT ); case CH_UNIT_VS: setwidths( num*width(line_gap(*style)), FIXED_UNIT ); case CH_UNIT_YU: setwidths( num*yunit(*style), FIXED_UNIT ); case CH_UNIT_ZU: setwidths( num*zunit(*style), FIXED_UNIT ); case CH_UNIT_WD: setwidths( num*FR, NEXT_UNIT ); case CH_UNIT_BD: setwidths( num*FR, FRAME_UNIT ); case CH_UNIT_RL: setwidths( num*FR, AVAIL_UNIT ); case CH_UNIT_DG: if( *res_inc == GAP_DEC ) num = - num; *res_inc = GAP_ABS; while( num > 180.0 ) num -= 360.0; while( num < -180.0 ) num += 360.0; assert((num>=-180.0) && (num<=180.0), "GetGap: dg!"); setwidths( num*DG, DEG_UNIT ); default: Error(17, 4, "units letter missing from %s", WARN, &fpos(x), string(x)); debug0(DGW, D, "GetGap failing (units letter missing)"); return; } if( units(*res_gap) == AVAIL_UNIT && w > FR ) { Error(17, 5, "%.1fr too large (1.0r substituted)", WARN, &fpos(x), num); w = FR; } /* don't have short lengths any more *** if( w > MAX_SHORT_LENGTH ) Error(17, 5, "%s exceeds maximum allowed gap size", INTERN, &fpos(x), string(x)); *** */ width(*res_gap) = w; /* read the optional gap mode */ switch( *str ) { case CH_NOBREAK: case '\0': mode(*res_gap) = EDGE_MODE; break; case CH_MODE_EDGE: mode(*res_gap) = EDGE_MODE; str++; break; case CH_MODE_HYPH: mode(*res_gap) = HYPH_MODE; str++; break; case CH_MODE_MARK: mode(*res_gap) = MARK_MODE; str++; break; case CH_MODE_OVER: mode(*res_gap) = OVER_MODE; str++; break; case CH_MODE_KERN: mode(*res_gap) = KERN_MODE; str++; break; case CH_MODE_TABL: mode(*res_gap) = TAB_MODE; str++; break; default: Error(17, 7, "unknown gap mode in %s", WARN, &fpos(x), string(x)); debug0(DGW, D, "GetGap failing (spacing mode)"); return; } /* read the optional nobreak */ if( *str == CH_NOBREAK ) { if( mode(*res_gap) == HYPH_MODE ) Error(17, 9, "replacing self-contradictory gap %s by breakable version", WARN, &fpos(x), string(x)); else nobreak(*res_gap) = TRUE; str++; } /* if string has not terminated, error */ if( *str != '\0' ) Error(17, 8, "invalid width or gap %s", WARN, &fpos(x), string(x)); debug2(DGW, D, "GetGap returning (res_gap = %s, res_inc = %s)", EchoGap(res_gap), Image( (int) *res_inc) ); } /* end GetGap */ /*@::MinGap()@****************************************************************/ /* */ /* FULL_LENGTH MinGap(a, b, c, xgap) */ /* */ /* Returns the minimum possible separation between the marks of two */ /* objects with the given intervening gap. */ /* The first object has fwd value a, the second has back value b and fwd c. */ /* */ /*****************************************************************************/ FULL_LENGTH MinGap(FULL_LENGTH a, FULL_LENGTH b, FULL_LENGTH c, GAP *xgap) { FULL_LENGTH res; int w = 0; switch( units(*xgap) ) { case FIXED_UNIT: w = width(*xgap); break; case FRAME_UNIT: w = 0; break; case AVAIL_UNIT: w = 0; break; case NEXT_UNIT: w = width(*xgap) * (b + c) / FR; break; default: assert(FALSE, "MinGap: units"); break; } switch( mode(*xgap) ) { case NO_MODE: assert(FALSE, "MinGap: NO_MODE"); res = 0; break; case ADD_HYPH: case HYPH_MODE: case EDGE_MODE: res = find_min(MAX_FULL_LENGTH, a + w + b); break; case MARK_MODE: if( BackEnd->fractional_spacing_avail ) res = find_max(w, a + b + (FULL_LENGTH) (0.1 * w) ); else res = find_max(w, a + b); break; case OVER_MODE: res = w; break; case KERN_MODE: res = find_max(find_max(a, b), w); break; case TAB_MODE: res = a + b; break; default: assert(FALSE, "MinGap: mode"); res = 0; break; } debug5(DGW, DD, "MinGap( _,%s %s %s,%s ) = %s", EchoLength(a), EchoGap(xgap), EchoLength(b), EchoLength(c), EchoLength(res) ); return res; } /* end MinGap */ /*@::ExtraGap()@**************************************************************/ /* */ /* FULL_LENGTH ExtraGap(a, b, xgap, dir) */ /* */ /* Consider two objects, the first with forward length a, the second with */ /* back length b. The objects are separated by the given gap. */ /* If dir == FWD, ExtraGap returns the maximum amount that a could be */ /* increased without increasing MinGap(a, b, c, xgap). */ /* If dir == BACK, similarly for b. */ /* */ /*****************************************************************************/ FULL_LENGTH ExtraGap(FULL_LENGTH a, FULL_LENGTH b, GAP *xgap, int dir) { FULL_LENGTH tmp, res; FULL_LENGTH w = units(*xgap) == FIXED_UNIT ? width(*xgap) : 0; switch( mode(*xgap) ) { case NO_MODE: assert(FALSE, "ExtraGap: NO_MODE"); res = 0; break; case ADD_HYPH: case HYPH_MODE: case EDGE_MODE: res = 0; break; case MARK_MODE: if( BackEnd->fractional_spacing_avail ) res = find_max(0, (FULL_LENGTH) (0.9 * w) - a - b); else res = find_max(0, w - a - b); break; case OVER_MODE: res = MAX_FULL_LENGTH; break; case KERN_MODE: tmp = find_max(a, find_max(b, w)); res = dir == BACK ? tmp - b : tmp - a; break; case TAB_MODE: res = 0; break; default: assert(FALSE, "ExtraGap: mode"); res = 0; break; } debug5(DGW, DD, "ExtraGap( %s, %s, %s, %s ) = %s", EchoLength(a), EchoLength(b), EchoGap(xgap), Image(dir), EchoLength(res)); return res; } /* end ExtraGap */ /*@::ActualGap()@*************************************************************/ /* */ /* FULL_LENGTH ActualGap(prevf, b, f, xgap, frame_size, mk) */ /* */ /* Returns the actual separation between the marks of an object of size */ /* (?, prevf) and an object of size (b, f) separated by gap *xgap in a */ /* frame of size frame_size; the first object lies at mk in the frame, */ /* where 0 <= mk <= frame_size. */ /* */ /*****************************************************************************/ FULL_LENGTH ActualGap(FULL_LENGTH prevf, FULL_LENGTH b, FULL_LENGTH f, GAP *xgap, FULL_LENGTH frame_size, FULL_LENGTH mk) { FULL_LENGTH res; int w = 0, w2; switch( units(*xgap) ) { case FIXED_UNIT: w = width(*xgap); break; case FRAME_UNIT: if( width(*xgap) > FR ) w = MAX_FULL_LENGTH; else w = (width(*xgap) * frame_size) / FR; break; case AVAIL_UNIT: w = (width(*xgap) * (frame_size - b - f)) / FR; w = find_max(w, 0); break; case NEXT_UNIT: w = width(*xgap) * (b + f) / FR; break; default: assert(FALSE, "ActualGap: units"); break; } switch( mode(*xgap) ) { case NO_MODE: Error(17, 10, "cannot continue after previous error(s)", FATAL, no_fpos); assert(FALSE, "ActualGap: NO_MODE"); w2 = 0; break; case ADD_HYPH: case HYPH_MODE: case EDGE_MODE: w2 = prevf + w + b; break; case MARK_MODE: if( BackEnd->fractional_spacing_avail ) w2 = find_max(w, prevf + b + (FULL_LENGTH) (0.1 * w) ); else w2 = find_max(w, prevf + b); break; case OVER_MODE: w2 = w; break; case KERN_MODE: w2 = find_max( find_max(prevf, b), w); break; case TAB_MODE: w2 = w + b - mk; w2 = find_max(w2, prevf + b ); break; default: assert(FALSE, "ActualGap: mode"); w2 = 0; break; } res = find_min(MAX_FULL_LENGTH, w2); debug7(DGW, DD, "ActualGap( _,%s %s %s,%s; frame_size %s, mk %s ) = %s", EchoLength(prevf), EchoGap(xgap), EchoLength(b), EchoLength(f), EchoLength(frame_size), EchoLength(mk), EchoLength(res)); return res; } /* end ActualGap */ /*@::EchoGap()@***************************************************************/ /* */ /* FULL_CHAR *EchoGap(xgap) */ /* */ /* Returns a static string showing the indicated xgap. */ /* */ /*****************************************************************************/ #if DEBUG_ON FULL_CHAR *EchoGap(GAP *xgap) { char *letter = "?ehxoktH"; char c; FULL_CHAR *res; char *u; static int i = 0; static char buff[3][20]; c = mode(*xgap) <= 7 ? letter[mode(*xgap)] : '?'; c = letter[mode(*xgap)]; u = nobreak(*xgap) ? "u" : ""; switch( units(*xgap) ) { case 0: sprintf(buff[i], "(none)%c", c); break; case FIXED_UNIT: sprintf(buff[i], "%.1fc%c%s", (float) width(*xgap)/CM, c,u); break; case NEXT_UNIT: sprintf(buff[i], "%.1fw%c%s", (float) width(*xgap)/FR, c,u); break; case FRAME_UNIT: sprintf(buff[i], "%.1fb%c%s", (float) width(*xgap)/FR, c,u); break; case AVAIL_UNIT: sprintf(buff[i], "%.1fr%c%s", (float) width(*xgap)/FR, c,u); break; case DEG_UNIT: sprintf(buff[i], "%.1fd", (float) width(*xgap) / DG); break; default: assert(FALSE, "EchoGap: units"); break; } res = AsciiToFull(buff[i]); i = (i + 1) % 3; return res; } /* end EchoGap */ #endif