| 1 51 package org.apache.fop.layout; 52 53 import java.util.ArrayList ; 55 import java.util.StringTokenizer ; 56 import java.awt.Rectangle ; 57 58 import org.apache.fop.datatypes.IDReferences; 60 import org.apache.fop.fo.properties.Hyphenate; 61 import org.apache.fop.fo.properties.LeaderAlignment; 62 import org.apache.fop.fo.properties.LeaderPattern; 63 import org.apache.fop.fo.properties.TextAlign; 64 import org.apache.fop.fo.properties.VerticalAlign; 65 import org.apache.fop.fo.properties.WhiteSpaceCollapse; 66 import org.apache.fop.fo.properties.WrapOption; 67 import org.apache.fop.layout.hyphenation.Hyphenation; 68 import org.apache.fop.layout.hyphenation.Hyphenator; 69 import org.apache.fop.layout.inline.*; 70 import org.apache.fop.messaging.MessageHandler; 71 import org.apache.fop.render.Renderer; 72 73 public class LineArea extends Area { 74 75 protected int lineHeight; 76 protected int halfLeading; 77 protected int nominalFontSize; 78 protected int nominalGlyphHeight; 79 80 protected int allocationHeight; 81 protected int startIndent; 82 protected int endIndent; 83 84 private int placementOffset; 85 private int textAlign; 86 87 private FontState currentFontState; private float red, green, blue; 90 private int wrapOption; 91 private int whiteSpaceCollapse; 92 private int vAlign; 93 94 95 private HyphenationProps hyphProps; 96 97 101 private int finalWidth = 0; 102 103 106 protected int embeddedLinkStart = 0; 107 108 109 111 112 protected static final int NOTHING = 0; 113 protected static final int WHITESPACE = 1; 114 protected static final int TEXT = 2; 115 protected static final int MULTIBYTECHAR = 3; 116 117 118 private int prev = NOTHING; 119 120 121 private int spaceWidth = 0; 122 123 128 private ArrayList pendingAreas = new ArrayList (); 129 130 131 private int pendingWidth = 0; 132 133 134 protected boolean prevUlState = false; 135 protected boolean prevOlState = false; 136 protected boolean prevLTState = false; 137 138 private boolean aligned = false; 141 private boolean hasPageNumbers = false; 142 143 public class Leader { 144 int leaderPattern; 145 int leaderLengthMinimum; 146 int leaderLengthOptimum; 147 int leaderLengthMaximum; 148 int ruleStyle; 149 int ruleThickness; 150 int leaderPatternWidth; 151 int leaderAlignment; 152 FontState fontState; 153 float red; 154 float green; 155 float blue; 156 int placementOffset; 157 int position; 158 159 Leader(int leaderPattern, int leaderLengthMinimum, 160 int leaderLengthOptimum, int leaderLengthMaximum, 161 int ruleStyle, int ruleThickness, 162 int leaderPatternWidth, int leaderAlignment, 163 FontState fontState, 164 float red, float green, float blue, 165 int placementOffset, 166 int position) { 167 this.leaderPattern=leaderPattern; 168 this.leaderLengthMinimum=leaderLengthMinimum; 169 this.leaderLengthOptimum=leaderLengthOptimum; 170 this.leaderLengthMaximum=leaderLengthMaximum; 171 this.ruleStyle=ruleStyle; 172 this.ruleThickness=ruleThickness; 173 this.leaderPatternWidth=leaderPatternWidth; 174 this.leaderAlignment=leaderAlignment; 175 this.fontState=fontState; 176 this.red=red; 177 this.green=green; 178 this.blue=blue; 179 this.placementOffset=placementOffset; 180 this.position = position; 181 } 182 void expand() { 183 char dot = '.'; 184 int dotWidth = fontState.getCharWidth(dot); 185 char space = ' '; 186 int spaceWidth = fontState.getCharWidth(space); 187 int idx=children.indexOf(this); 188 children.remove(this); 189 switch (leaderPattern) { 190 case LeaderPattern.SPACE: 191 InlineSpace spaceArea = new InlineSpace(leaderLengthOptimum 192 , false); 193 children.add(idx,spaceArea); 194 break; 195 case LeaderPattern.RULE: 196 LeaderArea leaderArea = new LeaderArea(fontState, red, green, 197 blue, "", 198 leaderLengthOptimum, 199 leaderPattern, 200 ruleThickness, 201 ruleStyle); 202 leaderArea.setYOffset(placementOffset); 203 children.add(idx,leaderArea); 204 break; 205 case LeaderPattern.DOTS: 206 if (this.leaderPatternWidth < dotWidth) { 209 this.leaderPatternWidth = 0; 210 } 211 if (this.leaderPatternWidth == 0) { 213 if (dotWidth == 0) { 214 MessageHandler.errorln("char " + dot 215 + " has width 0. Using width 100 instead."); 216 dotWidth = 100; 217 } 218 if (leaderAlignment == LeaderAlignment.REFERENCE_AREA) { 222 int nextRepeatedLeaderPatternCycle 223 = (int)Math.ceil((double)position/(double)dotWidth); 224 int spaceBeforeLeader = 225 dotWidth * nextRepeatedLeaderPatternCycle 226 - position; 227 if (spaceBeforeLeader > 0) { 231 children.add(idx, new InlineSpace(spaceBeforeLeader, 232 false)); 233 idx++; 234 leaderLengthOptimum -= spaceBeforeLeader; 238 } 239 } 240 int factor = (int)Math.floor(leaderLengthOptimum / dotWidth); 241 char[] leaderChars = new char[factor]; 242 for (int i = 0; i < factor; i++) { 243 leaderChars[i] = dot; 244 } 245 String leaderWord = new String (leaderChars); 246 int leaderWordWidth = fontState.getWordWidth(leaderWord); 247 WordArea leaderPatternArea = 248 new WordArea(fontState, red, green, blue, 249 leaderWord,leaderWordWidth); 250 leaderPatternArea.setYOffset(placementOffset); 251 children.add(idx, leaderPatternArea); 252 int spaceAfterLeader = leaderLengthOptimum 253 - leaderWordWidth; 254 if (spaceAfterLeader!=0) { 255 children.add(idx+1, new InlineSpace(spaceAfterLeader, 256 false)); 257 } 258 } else { 259 if (leaderAlignment == LeaderAlignment.REFERENCE_AREA) { 263 int nextRepeatedLeaderPatternCycle 264 = (int)Math.ceil((double)position/(double)leaderPatternWidth); 265 int spaceBeforeLeader = 266 leaderPatternWidth * nextRepeatedLeaderPatternCycle 267 - position; 268 if (spaceBeforeLeader > 0) { 272 children.add(idx, new InlineSpace(spaceBeforeLeader, 273 false)); 274 idx++; 275 leaderLengthOptimum -= spaceBeforeLeader; 279 } 280 } 281 int dotsFactor = 284 (int)Math.floor(((double)leaderLengthOptimum) 285 / ((double)leaderPatternWidth)); 286 for (int i = 0; i < dotsFactor; i++) { 289 InlineSpace spaceBetweenDots = 290 new InlineSpace(leaderPatternWidth - dotWidth, 291 false); 292 WordArea leaderPatternArea = 293 new WordArea(this.fontState, 294 this.red, this.green, this.blue, 295 new String ("."), dotWidth); 296 leaderPatternArea.setYOffset(placementOffset); 297 children.add(idx,leaderPatternArea); 298 idx++; 299 children.add(idx,spaceBetweenDots); 300 idx++; 301 } 302 children.add(idx,new InlineSpace(leaderLengthOptimum 304 - dotsFactor 305 * leaderPatternWidth)); 306 idx++; 307 } 308 break; 309 case LeaderPattern.USECONTENT: 311 MessageHandler.errorln("leader-pattern=\"use-content\" not " 312 + "supported by this version of Fop"); 313 return; 314 } 315 } 316 } 317 318 public LineArea(FontState fontState, int lineHeight, int halfLeading, 319 int allocationWidth, int startIndent, int endIndent, 320 LineArea prevLineArea) { 321 super(fontState); 322 323 this.currentFontState = fontState; 324 this.lineHeight = lineHeight; 325 this.nominalFontSize = fontState.getFontSize(); 326 this.nominalGlyphHeight = fontState.getAscender() 327 - fontState.getDescender(); 328 329 this.placementOffset = fontState.getAscender(); 330 this.contentRectangleWidth = allocationWidth - startIndent 331 - endIndent; 332 this.fontState = fontState; 333 334 this.allocationHeight = this.nominalGlyphHeight; 335 this.halfLeading = this.lineHeight - this.allocationHeight; 336 337 this.startIndent = startIndent; 338 this.endIndent = endIndent; 339 340 if (prevLineArea != null) { 341 boolean eatMoreSpace = true; 344 pendingWidth = prevLineArea.pendingWidth; 345 346 for (int i = 0; i < prevLineArea.pendingAreas.size(); i++) { 347 Object b = prevLineArea.pendingAreas.get(i); 348 if (eatMoreSpace) { 349 if (b instanceof InlineSpace) { 350 InlineSpace is = (InlineSpace)b; 351 if (is.isEatable()) { 352 pendingWidth -= is.getSize(); 353 } else { 354 eatMoreSpace = false; 355 pendingAreas.add(b); 356 } 357 } else { 358 eatMoreSpace = false; 359 pendingAreas.add(b); 360 } 361 } else { 362 pendingAreas.add(b); 363 } 364 } 365 prevLineArea.pendingWidth=0; 366 prevLineArea.pendingAreas=null; 367 } 368 } 369 370 public void render(Renderer renderer) { 371 if (pendingWidth > 0) { 372 MessageHandler.error("Areas pending, text probably lost in line" 373 + getLineText()); 374 } 375 if (hasPageNumbers) { 376 IDReferences idReferences = renderer.getIDReferences(); 377 for (int i = 0; i < children.size(); i++) { 378 Object o = children.get(i); 379 if ( o instanceof PageNumberInlineArea) { 380 PageNumberInlineArea pia = (PageNumberInlineArea)o; 381 FontState piaFontState = pia.getFontState(); 382 finalWidth-=piaFontState.getWordWidth(pia.getText()); 383 pia.resolve(idReferences); 384 finalWidth+=piaFontState.getWordWidth(pia.getText()); 385 } 386 } 387 } 388 if (!aligned) { 389 int padding = 0; 390 switch (textAlign) { 391 case TextAlign.START: padding = this.getContentWidth() - finalWidth; 393 endIndent += padding; 394 for (int i = 0; i < children.size(); i++) { 395 Object o = children.get(i); 396 if (o instanceof LineArea.Leader) { 397 LineArea.Leader leader = (LineArea.Leader)o; 398 leader.expand(); 399 } 400 } 401 break; 402 case TextAlign.END: padding = this.getContentWidth() - finalWidth; 404 startIndent += padding; 405 for (int i = 0; i < children.size(); i++) { 406 Object o = children.get(i); 407 if (o instanceof LineArea.Leader) { 408 LineArea.Leader leader = (LineArea.Leader)o; 409 leader.expand(); 410 } 411 } 412 break; 413 case TextAlign.CENTER: padding = (this.getContentWidth() - finalWidth) / 2; 415 startIndent += padding; 416 endIndent += padding; 417 for (int i = 0; i < children.size(); i++) { 418 Object o = children.get(i); 419 if (o instanceof LineArea.Leader) { 420 LineArea.Leader leader = (LineArea.Leader)o; 421 leader.expand(); 422 } 423 } 424 break; 425 case TextAlign.JUSTIFY: int leaderCount = 0; 428 int spaceCount = 0; 429 for (int i = 0; i < children.size(); i++ ) { 430 Object o = children.get(i); 431 if (o instanceof InlineSpace) { 432 InlineSpace space = (InlineSpace)o; 433 if (space.getResizeable()) { 434 spaceCount++; 435 } 436 } else if(o instanceof LineArea.Leader) { 437 leaderCount++; 438 } 439 } 440 padding = (this.getContentWidth() - finalWidth); 441 if (padding!=0) { 442 if (leaderCount>0) { 443 int offset=0; 444 for (int i = 0; i < children.size(); i++) { 445 Object o = children.get(i); 446 if (o instanceof LineArea.Leader) { 447 LineArea.Leader leader = (LineArea.Leader)o; 448 int leaderExpansionMaximum= 449 leader.leaderLengthMaximum - leader.leaderLengthOptimum; 450 int leaderExpansionMinimum= 451 leader.leaderLengthMinimum - leader.leaderLengthOptimum; 452 if (leaderExpansionMaximum < padding) { 453 leader.leaderLengthOptimum = 454 leader.leaderLengthMaximum; 455 leader.expand(); 456 padding-=leaderExpansionMaximum; 457 offset+=leaderExpansionMaximum; 458 } else if (padding < leaderExpansionMinimum) { 459 leader.leaderLengthOptimum = 460 leader.leaderLengthMinimum; 461 leader.expand(); 462 padding-=leaderExpansionMinimum; 463 offset+=leaderExpansionMinimum; 464 } else { 465 leader.leaderLengthOptimum += padding; 466 leader.expand(); 467 padding=0; 468 offset+=padding; 469 } 470 } else if (o instanceof InlineArea) { 471 ((InlineArea)o).setXOffset(((InlineArea)o).getXOffset() + offset); 472 } 473 } 474 } 475 if (padding != 0) { 476 if (spaceCount > 0) { 477 if (padding > 0) { 478 padding = padding/spaceCount; 487 spaceCount = 0; 488 for (int i = 0; i < children.size(); i++) { 490 Object o = children.get(i); 491 if (o instanceof InlineSpace) { 492 InlineSpace space = (InlineSpace)o; 493 if (space.getResizeable()) { 494 space.setSize(space.getSize() + padding); 495 spaceCount++; 496 } 497 } else if (o instanceof InlineArea) { 498 ((InlineArea)o).setXOffset(((InlineArea)o).getXOffset() + i * padding); 499 } 500 } 501 } else { 502 MessageHandler.log("Area overflow in line " 503 + getLineText()); 504 } 505 } else { 506 MessageHandler.log("No spaces to justify text in line " 508 + getLineText()); 509 } 510 } 511 } 512 break; 513 default: 514 MessageHandler.errorln("bad align: "+textAlign); 515 break; 516 } 517 aligned = true; 518 } 519 renderer.renderLineArea(this); 520 } 521 522 public int addPageNumberCitation(String refid, LinkSet ls) { 523 524 528 529 532 int width = 3*currentFontState.getCharWidth(' '); 533 534 535 PageNumberInlineArea pia 536 = new PageNumberInlineArea(currentFontState, 537 this.red, this.green, this.blue, 538 refid, width); 539 540 pia.setYOffset(placementOffset); 541 pendingAreas.add(pia); 542 pendingWidth += width; 543 prev = TEXT; 544 hasPageNumbers = true; 545 546 return -1; 547 } 548 549 550 555 public int addText(char data[], int start, int end, LinkSet ls, 556 TextState textState) { 557 if (start == -1) 560 return -1; 561 boolean overrun = false; 562 563 int wordStart = start; 564 int wordLength = 0; 565 int wordWidth = 0; 566 int whitespaceWidth = currentFontState.getCharWidth(' '); 568 569 boolean isText = false; 570 boolean isMultiByteChar = false; 571 572 573 for (int i = start; i < end; i++) { 574 int charWidth; 575 576 char c = data[i]; 577 if (!(isSpace(c) || (c == '\n') || (c == '\r') || (c == '\t') 578 || (c == '\u2028'))) { 579 charWidth = currentFontState.getCharWidth(c); 580 isText = true; 581 isMultiByteChar = (c > 127); 582 if (charWidth <= 0 && c != '\u200B' && c != '\uFEFF') 584 charWidth = whitespaceWidth; 585 } else { 586 if ((c == '\n') || (c == '\r') || (c == '\t')) 587 charWidth = whitespaceWidth; 588 else 589 charWidth = currentFontState.getCharWidth(c); 590 591 isText = false; 592 isMultiByteChar = false; 593 594 if (prev == WHITESPACE) { 595 596 598 if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) { 599 if (isSpace(c)) { 600 spaceWidth += currentFontState.getCharWidth(c); 601 } else if (c == '\n' || c == '\u2028') { 602 if (spaceWidth > 0) { 604 InlineSpace is = new InlineSpace(spaceWidth); 605 is.setUnderlined(textState.getUnderlined()); 606 is.setOverlined(textState.getOverlined()); 607 is.setLineThrough(textState.getLineThrough()); 608 addChild(is); 609 finalWidth += spaceWidth; 610 spaceWidth = 0; 611 } 612 return i + 1; 613 } else if (c == '\t') { 614 spaceWidth += 8 * whitespaceWidth; 615 } 616 } else if (c == '\u2028') { 617 if (spaceWidth > 0) { 620 InlineSpace is = new InlineSpace(spaceWidth); 621 is.setUnderlined(textState.getUnderlined()); 622 is.setOverlined(textState.getOverlined()); 623 is.setLineThrough(textState.getLineThrough()); 624 addChild(is); 625 finalWidth += spaceWidth; 626 spaceWidth = 0; 627 } 628 return i + 1; 629 } 630 631 } else if (prev == TEXT || prev == MULTIBYTECHAR ) { 632 633 638 if (spaceWidth > 0) { 639 InlineSpace is = new InlineSpace(spaceWidth); 640 if (prevUlState) { 641 is.setUnderlined(textState.getUnderlined()); 642 } 643 if (prevOlState) { 644 is.setOverlined(textState.getOverlined()); 645 } 646 if (prevLTState) { 647 is.setLineThrough(textState.getLineThrough()); 648 } 649 addChild(is); 650 finalWidth += spaceWidth; 651 spaceWidth = 0; 652 } 653 654 656 for (int j = 0; j < pendingAreas.size(); j++ ) { 657 Box box = (Box)pendingAreas.get(j); 658 if (box instanceof InlineArea) { 659 if (ls != null) { 660 Rectangle lr = 661 new Rectangle (finalWidth, 0, 662 ((InlineArea)box).getCont
|