@@ -457,22 +457,35 @@ impl<'a> Line<'a> {
457
457
self . items ( ) . skip ( start) . take ( end - start)
458
458
}
459
459
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.
461
462
fn justifiables ( & self ) -> usize {
462
463
let mut count = 0 ;
463
464
for shaped in self . items ( ) . filter_map ( Item :: text) {
464
465
count += shaped. justifiables ( ) ;
465
466
}
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
+
466
478
count
467
479
}
468
480
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 ( )
476
489
}
477
490
478
491
/// 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
835
848
836
849
// Cost parameters.
837
850
const HYPH_COST : Cost = 0.5 ;
838
- const CONSECUTIVE_DASH_COST : Cost = 30 .0;
851
+ const CONSECUTIVE_DASH_COST : Cost = 300 .0;
839
852
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 ;
842
854
843
855
// Dynamic programming table.
844
856
let mut active = 0 ;
@@ -864,14 +876,31 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
864
876
// Determine how much the line's spaces would need to be stretched
865
877
// to make it the desired width.
866
878
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
+ }
868
893
if ratio. is_infinite ( ) {
894
+ // The line's not stretchable, we calculate the ratio in another way...
869
895
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
+ }
870
902
}
871
903
872
- // At some point, it doesn't matter any more.
873
- ratio = ratio. min ( 10.0 ) ;
874
-
875
904
// Determine the cost of the line.
876
905
let min_ratio = if attempt. justify { MIN_RATIO } else { 0.0 } ;
877
906
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
883
912
active = i + 1 ;
884
913
MAX_COST
885
914
} 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.
889
918
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
+ }
891
924
} else {
892
925
// Normal line with cost of |ratio^3|.
893
926
ratio. powi ( 3 ) . abs ( )
@@ -898,6 +931,12 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
898
931
cost += HYPH_COST ;
899
932
}
900
933
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
+
901
940
// Penalize two consecutive dashes (not necessarily hyphens) extra.
902
941
if attempt. dash && pred. line . dash {
903
942
cost += CONSECUTIVE_DASH_COST ;
@@ -1233,13 +1272,32 @@ fn commit(
1233
1272
}
1234
1273
}
1235
1274
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.
1237
1280
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
+
1240
1297
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 ;
1243
1301
remaining = Abs :: zero ( ) ;
1244
1302
}
1245
1303
}
@@ -1275,7 +1333,7 @@ fn commit(
1275
1333
}
1276
1334
}
1277
1335
Item :: Text ( shaped) => {
1278
- let frame = shaped. build ( vt, justification ) ;
1336
+ let frame = shaped. build ( vt, justification_ratio , extra_justification ) ;
1279
1337
push ( & mut offset, frame) ;
1280
1338
}
1281
1339
Item :: Frame ( frame) => {
0 commit comments