@@ -457,22 +457,35 @@ impl<'a> Line<'a> {
457457 self . items ( ) . skip ( start) . take ( end - start)
458458 }
459459
460- /// How many justifiable glyphs the line contains.
460+ /// How many glyphs are in the text where we can insert additional
461+ /// space when encountering underfull lines.
461462 fn justifiables ( & self ) -> usize {
462463 let mut count = 0 ;
463464 for shaped in self . items ( ) . filter_map ( Item :: text) {
464465 count += shaped. justifiables ( ) ;
465466 }
467+ // CJK character at line end should not be adjusted.
468+ if self
469+ . items ( )
470+ . last ( )
471+ . and_then ( Item :: text)
472+ . map ( |s| s. cjk_justifiable_at_last ( ) )
473+ . unwrap_or ( false )
474+ {
475+ count -= 1 ;
476+ }
477+
466478 count
467479 }
468480
469- /// How much of the line is stretchable spaces.
470- fn stretch ( & self ) -> Abs {
471- let mut stretch = Abs :: zero ( ) ;
472- for shaped in self . items ( ) . filter_map ( Item :: text) {
473- stretch += shaped. stretch ( ) ;
474- }
475- stretch
481+ /// How much can the line stretch
482+ fn stretchability ( & self ) -> Abs {
483+ self . items ( ) . filter_map ( Item :: text) . map ( |s| s. stretchability ( ) ) . sum ( )
484+ }
485+
486+ /// How much can the line shrink
487+ fn shrinkability ( & self ) -> Abs {
488+ self . items ( ) . filter_map ( Item :: text) . map ( |s| s. shrinkability ( ) ) . sum ( )
476489 }
477490
478491 /// The sum of fractions in the line.
@@ -835,10 +848,9 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
835848
836849 // Cost parameters.
837850 const HYPH_COST : Cost = 0.5 ;
838- const CONSECUTIVE_DASH_COST : Cost = 30 .0;
851+ const CONSECUTIVE_DASH_COST : Cost = 300 .0;
839852 const MAX_COST : Cost = 1_000_000.0 ;
840- const MIN_COST : Cost = -MAX_COST ;
841- const MIN_RATIO : f64 = -0.15 ;
853+ const MIN_RATIO : f64 = -1.0 ;
842854
843855 // Dynamic programming table.
844856 let mut active = 0 ;
@@ -864,14 +876,31 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
864876 // Determine how much the line's spaces would need to be stretched
865877 // to make it the desired width.
866878 let delta = width - attempt. width ;
867- let mut ratio = delta / attempt. stretch ( ) ;
879+ // Determine how much stretch are permitted.
880+ let adjust = if delta >= Abs :: zero ( ) {
881+ attempt. stretchability ( )
882+ } else {
883+ attempt. shrinkability ( )
884+ } ;
885+ // Ideally, the ratio should between -1.0 and 1.0, but sometimes a value above 1.0
886+ // is possible, in which case the line is underfull.
887+ let mut ratio = delta / adjust;
888+ if ratio. is_nan ( ) {
889+ // The line is not stretchable, but it just fits.
890+ // This often happens with monospace fonts and CJK texts.
891+ ratio = 0.0 ;
892+ }
868893 if ratio. is_infinite ( ) {
894+ // The line's not stretchable, we calculate the ratio in another way...
869895 ratio = delta / ( em / 2.0 ) ;
896+ // ...and because it is underfull/overfull, make sure the ratio is at least 1.0.
897+ if ratio > 0.0 {
898+ ratio += 1.0 ;
899+ } else {
900+ ratio -= 1.0 ;
901+ }
870902 }
871903
872- // At some point, it doesn't matter any more.
873- ratio = ratio. min ( 10.0 ) ;
874-
875904 // Determine the cost of the line.
876905 let min_ratio = if attempt. justify { MIN_RATIO } else { 0.0 } ;
877906 let mut cost = if ratio < min_ratio {
@@ -883,11 +912,15 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
883912 active = i + 1 ;
884913 MAX_COST
885914 } else if mandatory || eof {
886- // This is a mandatory break and the line is not overfull, so it
887- // has minimum cost. All breakpoints before this one become
888- // inactive since no line can span above the mandatory break.
915+ // This is a mandatory break and the line is not overfull, so
916+ // all breakpoints before this one become inactive since no line
917+ // can span above the mandatory break.
889918 active = k;
890- MIN_COST + if attempt. justify { ratio. powi ( 3 ) . abs ( ) } else { 0.0 }
919+ if attempt. justify {
920+ ratio. powi ( 3 ) . abs ( )
921+ } else {
922+ 0.0
923+ }
891924 } else {
892925 // Normal line with cost of |ratio^3|.
893926 ratio. powi ( 3 ) . abs ( )
@@ -898,6 +931,12 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
898931 cost += HYPH_COST ;
899932 }
900933
934+ // In Knuth paper, cost = (1 + 100|r|^3 + p)^2 + a,
935+ // where r is the ratio, p=50 is penaty, and a=3000 is consecutive penaty.
936+ // We divide the whole formula by 10, resulting (0.01 + |r|^3 + p)^2 + a,
937+ // where p=0.5 and a=300
938+ cost = ( 0.01 + cost) . powi ( 2 ) ;
939+
901940 // Penalize two consecutive dashes (not necessarily hyphens) extra.
902941 if attempt. dash && pred. line . dash {
903942 cost += CONSECUTIVE_DASH_COST ;
@@ -1233,13 +1272,32 @@ fn commit(
12331272 }
12341273 }
12351274
1236- // Determine how much to justify each space.
1275+ // Determine how much addtional space is needed.
1276+ // The justicication_ratio is for the first step justification,
1277+ // extra_justification is for the last step.
1278+ // For more info on multi-step justification, see Procedures for Inter-
1279+ // Character Space Expansion in W3C document Chinese Layout Requirements.
12371280 let fr = line. fr ( ) ;
1238- let mut justification = Abs :: zero ( ) ;
1239- if remaining < Abs :: zero ( ) || ( line. justify && fr. is_zero ( ) ) {
1281+ let mut justification_ratio = 0.0 ;
1282+ let mut extra_justification = Abs :: zero ( ) ;
1283+
1284+ let shrink = line. shrinkability ( ) ;
1285+ let stretch = line. stretchability ( ) ;
1286+ if remaining < Abs :: zero ( ) && shrink > Abs :: zero ( ) {
1287+ // Attempt to reduce the length of the line, using shrinkability.
1288+ justification_ratio = ( remaining / shrink) . max ( -1.0 ) ;
1289+ remaining = ( remaining + shrink) . min ( Abs :: zero ( ) ) ;
1290+ } else if line. justify && fr. is_zero ( ) {
1291+ // Attempt to increase the length of the line, using stretchability.
1292+ if stretch > Abs :: zero ( ) {
1293+ justification_ratio = ( remaining / stretch) . min ( 1.0 ) ;
1294+ remaining = ( remaining - stretch) . max ( Abs :: zero ( ) ) ;
1295+ }
1296+
12401297 let justifiables = line. justifiables ( ) ;
1241- if justifiables > 0 {
1242- justification = remaining / justifiables as f64 ;
1298+ if justifiables > 0 && remaining > Abs :: zero ( ) {
1299+ // Underfull line, distribute the extra space.
1300+ extra_justification = remaining / justifiables as f64 ;
12431301 remaining = Abs :: zero ( ) ;
12441302 }
12451303 }
@@ -1275,7 +1333,7 @@ fn commit(
12751333 }
12761334 }
12771335 Item :: Text ( shaped) => {
1278- let frame = shaped. build ( vt, justification ) ;
1336+ let frame = shaped. build ( vt, justification_ratio , extra_justification ) ;
12791337 push ( & mut offset, frame) ;
12801338 }
12811339 Item :: Frame ( frame) => {
0 commit comments