You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LineArea.java 50KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335
  1. /*
  2. * $Id$
  3. * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
  4. * For details on use and redistribution please refer to the
  5. * LICENSE file included with these sources.
  6. */
  7. package org.apache.fop.layout;
  8. // fop
  9. import org.apache.fop.render.Renderer;
  10. import org.apache.fop.messaging.MessageHandler;
  11. import org.apache.fop.layout.inline.*;
  12. import org.apache.fop.datatypes.IDNode;
  13. import org.apache.fop.fo.properties.WrapOption;
  14. import org.apache.fop.fo.properties.WhiteSpaceCollapse;
  15. import org.apache.fop.fo.properties.TextAlign;
  16. import org.apache.fop.fo.properties.TextAlignLast;
  17. import org.apache.fop.fo.properties.LeaderPattern;
  18. import org.apache.fop.fo.properties.Hyphenate;
  19. import org.apache.fop.fo.properties.CountryMaker;
  20. import org.apache.fop.fo.properties.LanguageMaker;
  21. import org.apache.fop.fo.properties.LeaderAlignment;
  22. import org.apache.fop.fo.properties.VerticalAlign;
  23. import org.apache.fop.layout.hyphenation.Hyphenation;
  24. import org.apache.fop.layout.hyphenation.Hyphenator;
  25. import org.apache.fop.configuration.Configuration;
  26. // java
  27. import java.util.Vector;
  28. import java.util.Enumeration;
  29. import java.util.StringTokenizer;
  30. import java.awt.Rectangle;
  31. public class LineArea extends Area {
  32. protected int lineHeight;
  33. protected int halfLeading;
  34. protected int nominalFontSize;
  35. protected int nominalGlyphHeight;
  36. protected int allocationHeight;
  37. protected int startIndent;
  38. protected int endIndent;
  39. private int placementOffset;
  40. private FontState currentFontState; // not the nominal, which is
  41. // in this.fontState
  42. private float red, green, blue;
  43. private int wrapOption;
  44. private int whiteSpaceCollapse;
  45. int vAlign;
  46. /* hyphenation */
  47. HyphenationProps hyphProps;
  48. /*
  49. * the width of text that has definitely made it into the line
  50. * area
  51. */
  52. protected int finalWidth = 0;
  53. /* the position to shift a link rectangle in order to compensate for links embedded within a word */
  54. protected int embeddedLinkStart = 0;
  55. /* the width of the current word so far */
  56. // protected int wordWidth = 0;
  57. /* values that prev (below) may take */
  58. protected static final int NOTHING = 0;
  59. protected static final int WHITESPACE = 1;
  60. protected static final int TEXT = 2;
  61. /* the character type of the previous character */
  62. protected int prev = NOTHING;
  63. /* the position in data[] of the start of the current word */
  64. // protected int wordStart;
  65. /* the length (in characters) of the current word */
  66. // protected int wordLength = 0;
  67. /* width of spaces before current word */
  68. protected int spaceWidth = 0;
  69. /*
  70. * the inline areas that have not yet been added to the line
  71. * because subsequent characters to come (in a different addText)
  72. * may be part of the same word
  73. */
  74. protected Vector pendingAreas = new Vector();
  75. /* the width of the pendingAreas */
  76. protected int pendingWidth = 0;
  77. /* text-decoration of the previous text */
  78. protected boolean prevUlState = false;
  79. protected boolean prevOlState = false;
  80. protected boolean prevLTState = false;
  81. public LineArea(FontState fontState, int lineHeight, int halfLeading,
  82. int allocationWidth, int startIndent, int endIndent,
  83. LineArea prevLineArea) {
  84. super(fontState);
  85. this.currentFontState = fontState;
  86. this.lineHeight = lineHeight;
  87. this.nominalFontSize = fontState.getFontSize();
  88. this.nominalGlyphHeight = fontState.getAscender()
  89. - fontState.getDescender();
  90. this.placementOffset = fontState.getAscender();
  91. this.contentRectangleWidth = allocationWidth - startIndent
  92. - endIndent;
  93. this.fontState = fontState;
  94. this.allocationHeight = this.nominalGlyphHeight;
  95. this.halfLeading = this.lineHeight - this.allocationHeight;
  96. this.startIndent = startIndent;
  97. this.endIndent = endIndent;
  98. if (prevLineArea != null) {
  99. Enumeration e = prevLineArea.pendingAreas.elements();
  100. Box b = null;
  101. // There might be InlineSpaces at the beginning
  102. // that should not be there - eat them
  103. boolean eatMoreSpace = true;
  104. int eatenWidth = 0;
  105. while (eatMoreSpace) {
  106. if (e.hasMoreElements()) {
  107. b = (Box)e.nextElement();
  108. if (b instanceof InlineSpace) {
  109. InlineSpace is = (InlineSpace)b;
  110. if (is.isEatable())
  111. eatenWidth += is.getSize();
  112. else
  113. eatMoreSpace = false;
  114. } else {
  115. eatMoreSpace = false;
  116. }
  117. } else {
  118. eatMoreSpace = false;
  119. b = null;
  120. }
  121. }
  122. while (b != null) {
  123. pendingAreas.addElement(b);
  124. if (e.hasMoreElements())
  125. b = (Box)e.nextElement();
  126. else
  127. b = null;
  128. }
  129. pendingWidth = prevLineArea.getPendingWidth() - eatenWidth;
  130. }
  131. }
  132. public int addPageNumberCitation(String refid, LinkSet ls) {
  133. /*
  134. * We should add code here to handle the case where the page number doesn't fit on the current line
  135. */
  136. // Space must be alloted to the page number, so currently we give it 3 spaces
  137. int width = currentFontState.width(currentFontState.mapChar(' '));
  138. PageNumberInlineArea pia = new PageNumberInlineArea(currentFontState,
  139. this.red, this.green, this.blue, refid, width);
  140. pia.setYOffset(placementOffset);
  141. pendingAreas.addElement(pia);
  142. pendingWidth += width;
  143. prev = TEXT;
  144. return -1;
  145. }
  146. /**
  147. * adds text to line area
  148. *
  149. * @return int character position
  150. */
  151. public int addText(char odata[], int start, int end, LinkSet ls,
  152. TextState textState) {
  153. // this prevents an array index out of bounds
  154. // which occurs when some text is laid out again.
  155. if (start == -1)
  156. return -1;
  157. boolean overrun = false;
  158. int wordStart = start;
  159. int wordLength = 0;
  160. int wordWidth = 0;
  161. // With CID fonts, space isn't neccesary currentFontState.width(32)
  162. int whitespaceWidth = getCharWidth(' ');
  163. char[] data = new char[odata.length];
  164. char[] dataCopy = new char[odata.length];
  165. System.arraycopy(odata, 0, data, 0, odata.length);
  166. System.arraycopy(odata, 0, dataCopy, 0, odata.length);
  167. boolean isText = false;
  168. /* iterate over each character */
  169. for (int i = start; i < end; i++) {
  170. int charWidth;
  171. /* get the character */
  172. char c = data[i];
  173. if (!(isSpace(c) || (c == '\n') || (c == '\r') || (c == '\t')
  174. || (c == '\u2028'))) {
  175. charWidth = getCharWidth(c);
  176. isText = true;
  177. // Add support for zero-width spaces
  178. if (charWidth <= 0 && c != '\u200B' && c != '\uFEFF')
  179. charWidth = whitespaceWidth;
  180. } else {
  181. if ((c == '\n') || (c == '\r') || (c == '\t'))
  182. charWidth = whitespaceWidth;
  183. else
  184. charWidth = getCharWidth(c);
  185. isText = false;
  186. if (prev == WHITESPACE) {
  187. // if current & previous are WHITESPACE
  188. if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) {
  189. if (isSpace(c)) {
  190. spaceWidth += getCharWidth(c);
  191. } else if (c == '\n' || c == '\u2028') {
  192. // force line break
  193. if (spaceWidth > 0) {
  194. InlineSpace is = new InlineSpace(spaceWidth);
  195. is.setUnderlined(textState.getUnderlined());
  196. is.setOverlined(textState.getOverlined());
  197. is.setLineThrough(textState.getLineThrough());
  198. addChild(is);
  199. finalWidth += spaceWidth;
  200. spaceWidth = 0;
  201. }
  202. return i + 1;
  203. } else if (c == '\t') {
  204. spaceWidth += 8 * whitespaceWidth;
  205. }
  206. } else if (c == '\u2028') {
  207. // Line separator
  208. // Breaks line even if WhiteSpaceCollapse = True
  209. if (spaceWidth > 0) {
  210. InlineSpace is = new InlineSpace(spaceWidth);
  211. is.setUnderlined(textState.getUnderlined());
  212. is.setOverlined(textState.getOverlined());
  213. is.setLineThrough(textState.getLineThrough());
  214. addChild(is);
  215. finalWidth += spaceWidth;
  216. spaceWidth = 0;
  217. }
  218. return i + 1;
  219. }
  220. } else if (prev == TEXT) {
  221. // if current is WHITESPACE and previous TEXT
  222. // the current word made it, so
  223. // add the space before the current word (if there
  224. // was some)
  225. if (spaceWidth > 0) {
  226. InlineSpace is = new InlineSpace(spaceWidth);
  227. if (prevUlState) {
  228. is.setUnderlined(textState.getUnderlined());
  229. }
  230. if (prevOlState) {
  231. is.setOverlined(textState.getOverlined());
  232. }
  233. if (prevLTState) {
  234. is.setLineThrough(textState.getLineThrough());
  235. }
  236. addChild(is);
  237. finalWidth += spaceWidth;
  238. spaceWidth = 0;
  239. }
  240. // add any pending areas
  241. Enumeration e = pendingAreas.elements();
  242. while (e.hasMoreElements()) {
  243. Box box = (Box)e.nextElement();
  244. if (box instanceof InlineArea) {
  245. if (ls != null) {
  246. Rectangle lr =
  247. new Rectangle(finalWidth, 0,
  248. ((InlineArea)box).getContentWidth(),
  249. fontState.getFontSize());
  250. ls.addRect(lr, this, (InlineArea)box);
  251. }
  252. }
  253. addChild(box);
  254. }
  255. finalWidth += pendingWidth;
  256. // reset pending areas array
  257. pendingWidth = 0;
  258. pendingAreas = new Vector();
  259. // add the current word
  260. if (wordLength > 0) {
  261. // The word might contain nonbreaking
  262. // spaces. Split the word and add InlineSpace
  263. // as necessary. All spaces inside the word
  264. // Have a fixed width.
  265. addSpacedWord(new String(data, wordStart, wordLength),
  266. ls, finalWidth, 0, textState, false);
  267. finalWidth += wordWidth;
  268. // reset word width
  269. wordWidth = 0;
  270. }
  271. // deal with this new whitespace following the
  272. // word we just added
  273. prev = WHITESPACE;
  274. embeddedLinkStart =
  275. 0; // reset embeddedLinkStart since a space was encountered
  276. spaceWidth = getCharWidth(c);
  277. /*
  278. * here is the place for space-treatment value 'ignore':
  279. * if (this.spaceTreatment ==
  280. * SpaceTreatment.IGNORE) {
  281. * // do nothing
  282. * } else {
  283. * spaceWidth = currentFontState.width(32);
  284. * }
  285. */
  286. if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) {
  287. if (c == '\n' || c == '\u2028') {
  288. // force a line break
  289. return i + 1;
  290. } else if (c == '\t') {
  291. spaceWidth = whitespaceWidth;
  292. }
  293. } else if (c == '\u2028') {
  294. return i + 1;
  295. }
  296. } else {
  297. // if current is WHITESPACE and no previous
  298. if (this.whiteSpaceCollapse == WhiteSpaceCollapse.FALSE) {
  299. if (isSpace(c)) {
  300. prev = WHITESPACE;
  301. spaceWidth = getCharWidth(c);
  302. } else if (c == '\n') {
  303. // force line break
  304. // textdecoration not used because spaceWidth is 0
  305. InlineSpace is = new InlineSpace(spaceWidth);
  306. addChild(is);
  307. return i + 1;
  308. } else if (c == '\t') {
  309. prev = WHITESPACE;
  310. spaceWidth = 8 * whitespaceWidth;
  311. }
  312. } else {
  313. // skip over it
  314. wordStart++;
  315. }
  316. }
  317. }
  318. if (isText) { // current is TEXT
  319. if (prev == WHITESPACE) {
  320. // if current is TEXT and previous WHITESPACE
  321. wordWidth = charWidth;
  322. if ((finalWidth + spaceWidth + wordWidth)
  323. > this.getContentWidth()) {
  324. if (overrun)
  325. MessageHandler.log("area contents overflows area");
  326. if (this.wrapOption == WrapOption.WRAP) {
  327. return i;
  328. }
  329. }
  330. prev = TEXT;
  331. wordStart = i;
  332. wordLength = 1;
  333. } else if (prev == TEXT) {
  334. wordLength++;
  335. wordWidth += charWidth;
  336. } else { // nothing previous
  337. prev = TEXT;
  338. wordStart = i;
  339. wordLength = 1;
  340. wordWidth = charWidth;
  341. }
  342. if ((finalWidth + spaceWidth + pendingWidth + wordWidth)
  343. > this.getContentWidth()) {
  344. // BREAK MID WORD
  345. if (canBreakMidWord()) {
  346. addSpacedWord(new String(data, wordStart, wordLength - 1),
  347. ls,
  348. finalWidth + spaceWidth
  349. + embeddedLinkStart, spaceWidth,
  350. textState, false);
  351. finalWidth += wordWidth;
  352. wordWidth = 0;
  353. return i;
  354. }
  355. if (this.wrapOption == WrapOption.WRAP) {
  356. if (hyphProps.hyphenate == Hyphenate.TRUE) {
  357. int ret = wordStart;
  358. ret = this.doHyphenation(dataCopy, i, wordStart,
  359. this.getContentWidth()
  360. - (finalWidth
  361. + spaceWidth
  362. + pendingWidth));
  363. // current word couldn't be hypenated
  364. // couldn't fit first word
  365. // I am at the beginning of my line
  366. if ((ret == wordStart) &&
  367. (wordStart == start) &&
  368. (finalWidth == 0)) {
  369. MessageHandler.log("area contents overflows area");
  370. addSpacedWord(new String(data, wordStart, wordLength - 1),
  371. ls,
  372. finalWidth + spaceWidth
  373. + embeddedLinkStart,
  374. spaceWidth, textState, false);
  375. finalWidth += wordWidth;
  376. wordWidth = 0;
  377. ret = i;
  378. }
  379. return ret;
  380. } else if (wordStart == start) {
  381. // first word
  382. overrun = true;
  383. // if not at start of line, return word start
  384. // to try again on a new line
  385. if (finalWidth > 0) {
  386. return wordStart;
  387. }
  388. } else {
  389. return wordStart;
  390. }
  391. }
  392. }
  393. }
  394. } // end of iteration over text
  395. if (prev == TEXT) {
  396. if (spaceWidth > 0) {
  397. InlineSpace pis = new InlineSpace(spaceWidth);
  398. // Make sure that this space doesn't occur as
  399. // first thing in the next line
  400. pis.setEatable(true);
  401. if (prevUlState) {
  402. pis.setUnderlined(textState.getUnderlined());
  403. }
  404. if (prevOlState) {
  405. pis.setOverlined(textState.getOverlined());
  406. }
  407. if (prevLTState) {
  408. pis.setLineThrough(textState.getLineThrough());
  409. }
  410. pendingAreas.addElement(pis);
  411. pendingWidth += spaceWidth;
  412. spaceWidth = 0;
  413. }
  414. addSpacedWord(new String(data, wordStart, wordLength), ls,
  415. finalWidth + spaceWidth + embeddedLinkStart,
  416. spaceWidth, textState, true);
  417. embeddedLinkStart += wordWidth;
  418. wordWidth = 0;
  419. }
  420. if (overrun)
  421. MessageHandler.log("area contents overflows area");
  422. return -1;
  423. }
  424. /**
  425. * adds a Leader; actually the method receives the leader properties
  426. * and creates a leader area or an inline area which is appended to
  427. * the children of the containing line area. <br>
  428. * leader pattern use-content is not implemented.
  429. */
  430. public void addLeader(int leaderPattern, int leaderLengthMinimum,
  431. int leaderLengthOptimum, int leaderLengthMaximum,
  432. int ruleStyle, int ruleThickness,
  433. int leaderPatternWidth, int leaderAlignment) {
  434. WordArea leaderPatternArea;
  435. int leaderLength = 0;
  436. char dotIndex = '.'; // currentFontState.mapChar('.');
  437. int dotWidth =
  438. currentFontState.width(currentFontState.mapChar(dotIndex));
  439. char whitespaceIndex = ' '; // currentFontState.mapChar(' ');
  440. int whitespaceWidth =
  441. currentFontState.width(currentFontState.mapChar(whitespaceIndex));
  442. int remainingWidth = this.getContentWidth()
  443. - this.getCurrentXPosition();
  444. /**
  445. * checks whether leaderLenghtOptimum fits into rest of line;
  446. * should never overflow, as it has been checked already in BlockArea
  447. * first check: use remaining width if it smaller than optimum oder maximum
  448. */
  449. if ((remainingWidth <= leaderLengthOptimum)
  450. || (remainingWidth <= leaderLengthMaximum)) {
  451. leaderLength = remainingWidth;
  452. } else if ((remainingWidth > leaderLengthOptimum)
  453. && (remainingWidth > leaderLengthMaximum)) {
  454. leaderLength = leaderLengthMaximum;
  455. } else if ((leaderLengthOptimum > leaderLengthMaximum)
  456. && (leaderLengthOptimum < remainingWidth)) {
  457. leaderLength = leaderLengthOptimum;
  458. }
  459. // stop if leader-length is too small
  460. if (leaderLength <= 0) {
  461. return;
  462. }
  463. switch (leaderPattern) {
  464. case LeaderPattern.SPACE:
  465. InlineSpace spaceArea = new InlineSpace(leaderLength);
  466. pendingAreas.addElement(spaceArea);
  467. break;
  468. case LeaderPattern.RULE:
  469. LeaderArea leaderArea = new LeaderArea(fontState, red, green,
  470. blue, "", leaderLength,
  471. leaderPattern,
  472. ruleThickness, ruleStyle);
  473. leaderArea.setYOffset(placementOffset);
  474. pendingAreas.addElement(leaderArea);
  475. break;
  476. case LeaderPattern.DOTS:
  477. // if the width of a dot is larger than leader-pattern-width
  478. // ignore this property
  479. if (leaderPatternWidth < dotWidth) {
  480. leaderPatternWidth = 0;
  481. }
  482. // if value of leader-pattern-width is 'use-font-metrics' (0)
  483. if (leaderPatternWidth == 0) {
  484. pendingAreas.addElement(this.buildSimpleLeader(dotIndex,
  485. leaderLength));
  486. } else {
  487. // if leader-alignment is used, calculate space to insert before leader
  488. // so that all dots will be parallel.
  489. if (leaderAlignment == LeaderAlignment.REFERENCE_AREA) {
  490. int spaceBeforeLeader =
  491. this.getLeaderAlignIndent(leaderLength,
  492. leaderPatternWidth);
  493. // appending indent space leader-alignment
  494. // setting InlineSpace to false, so it is not used in line justification
  495. if (spaceBeforeLeader != 0) {
  496. pendingAreas.addElement(new InlineSpace(spaceBeforeLeader,
  497. false));
  498. pendingWidth += spaceBeforeLeader;
  499. // shorten leaderLength, otherwise - in case of
  500. // leaderLength=remaining length - it will cut off the end of
  501. // leaderlength
  502. leaderLength -= spaceBeforeLeader;
  503. }
  504. }
  505. // calculate the space to insert between the dots and create a
  506. // inline area with this width
  507. InlineSpace spaceBetweenDots =
  508. new InlineSpace(leaderPatternWidth - dotWidth, false);
  509. leaderPatternArea = new WordArea(currentFontState, this.red,
  510. this.green, this.blue,
  511. new String("."), dotWidth);
  512. leaderPatternArea.setYOffset(placementOffset);
  513. int dotsFactor =
  514. (int)Math.floor(((double)leaderLength)
  515. / ((double)leaderPatternWidth));
  516. // add combination of dot + space to fill leader
  517. // is there a way to do this in a more effective way?
  518. for (int i = 0; i < dotsFactor; i++) {
  519. pendingAreas.addElement(leaderPatternArea);
  520. pendingAreas.addElement(spaceBetweenDots);
  521. }
  522. // append at the end some space to fill up to leader length
  523. pendingAreas.addElement(new InlineSpace(leaderLength
  524. - dotsFactor
  525. * leaderPatternWidth));
  526. }
  527. break;
  528. // leader pattern use-content not implemented.
  529. case LeaderPattern.USECONTENT:
  530. MessageHandler.errorln("leader-pattern=\"use-content\" not "
  531. + "supported by this version of Fop");
  532. return;
  533. }
  534. // adds leader length to length of pending inline areas
  535. pendingWidth += leaderLength;
  536. // sets prev to TEXT and makes so sure, that also blocks only
  537. // containing leaders are processed
  538. prev = TEXT;
  539. }
  540. /**
  541. * adds pending inline areas to the line area
  542. * normally done, when the line area is filled and
  543. * added as child to the parent block area
  544. */
  545. public void addPending() {
  546. if (spaceWidth > 0) {
  547. addChild(new InlineSpace(spaceWidth));
  548. finalWidth += spaceWidth;
  549. spaceWidth = 0;
  550. }
  551. Enumeration e = pendingAreas.elements();
  552. while (e.hasMoreElements()) {
  553. Box box = (Box)e.nextElement();
  554. addChild(box);
  555. }
  556. finalWidth += pendingWidth;
  557. // reset pending areas array
  558. pendingWidth = 0;
  559. pendingAreas = new Vector();
  560. }
  561. /**
  562. * aligns line area
  563. *
  564. */
  565. public void align(int type) {
  566. int padding = 0;
  567. switch (type) {
  568. case TextAlign.START: // left
  569. padding = this.getContentWidth() - finalWidth;
  570. endIndent += padding;
  571. break;
  572. case TextAlign.END: // right
  573. padding = this.getContentWidth() - finalWidth;
  574. startIndent += padding;
  575. break;
  576. case TextAlign.CENTER: // center
  577. padding = (this.getContentWidth() - finalWidth) / 2;
  578. startIndent += padding;
  579. endIndent += padding;
  580. break;
  581. case TextAlign.JUSTIFY: // justify
  582. // first pass - count the spaces
  583. int spaceCount = 0;
  584. Enumeration e = children.elements();
  585. while (e.hasMoreElements()) {
  586. Box b = (Box)e.nextElement();
  587. if (b instanceof InlineSpace) {
  588. InlineSpace space = (InlineSpace)b;
  589. if (space.getResizeable()) {
  590. spaceCount++;
  591. }
  592. }
  593. }
  594. if (spaceCount > 0) {
  595. padding = (this.getContentWidth() - finalWidth) / spaceCount;
  596. } else { // no spaces
  597. padding = 0;
  598. }
  599. // second pass - add additional space
  600. spaceCount = 0;
  601. e = children.elements();
  602. while (e.hasMoreElements()) {
  603. Box b = (Box)e.nextElement();
  604. if (b instanceof InlineSpace) {
  605. InlineSpace space = (InlineSpace)b;
  606. if (space.getResizeable()) {
  607. space.setSize(space.getSize() + padding);
  608. spaceCount++;
  609. }
  610. } else if (b instanceof InlineArea) {
  611. ((InlineArea)b).setXOffset(spaceCount * padding);
  612. }
  613. }
  614. }
  615. }
  616. /**
  617. * Balance (vertically) the inline areas within this line.
  618. */
  619. public void verticalAlign() {
  620. int superHeight = -this.placementOffset;
  621. int maxHeight = this.allocationHeight;
  622. Enumeration e = children.elements();
  623. while (e.hasMoreElements()) {
  624. Box b = (Box)e.nextElement();
  625. if (b instanceof InlineArea) {
  626. InlineArea ia = (InlineArea)b;
  627. if (ia instanceof WordArea) {
  628. ia.setYOffset(placementOffset);
  629. }
  630. if (ia.getHeight() > maxHeight) {
  631. maxHeight = ia.getHeight();
  632. }
  633. int vert = ia.getVerticalAlign();
  634. if (vert == VerticalAlign.SUPER) {
  635. int fh = fontState.getAscender();
  636. ia.setYOffset((int)(placementOffset - (2 * fh / 3.0)));
  637. } else if (vert == VerticalAlign.SUB) {
  638. int fh = fontState.getAscender();
  639. ia.setYOffset((int)(placementOffset + (2 * fh / 3.0)));
  640. }
  641. } else {}
  642. }
  643. // adjust the height of this line to the
  644. // resulting alignment height.
  645. this.allocationHeight = maxHeight;
  646. }
  647. public void changeColor(float red, float green, float blue) {
  648. this.red = red;
  649. this.green = green;
  650. this.blue = blue;
  651. }
  652. public void changeFont(FontState fontState) {
  653. this.currentFontState = fontState;
  654. }
  655. public void changeWhiteSpaceCollapse(int whiteSpaceCollapse) {
  656. this.whiteSpaceCollapse = whiteSpaceCollapse;
  657. }
  658. public void changeWrapOption(int wrapOption) {
  659. this.wrapOption = wrapOption;
  660. }
  661. public void changeVerticalAlign(int vAlign) {
  662. this.vAlign = vAlign;
  663. }
  664. public int getEndIndent() {
  665. return endIndent;
  666. }
  667. public int getHeight() {
  668. return this.allocationHeight;
  669. }
  670. public int getPlacementOffset() {
  671. return this.placementOffset;
  672. }
  673. public int getStartIndent() {
  674. return startIndent;
  675. }
  676. public boolean isEmpty() {
  677. return !(pendingAreas.size() > 0 || children.size() > 0);
  678. // return (prev == NOTHING);
  679. }
  680. public Vector getPendingAreas() {
  681. return pendingAreas;
  682. }
  683. public int getPendingWidth() {
  684. return pendingWidth;
  685. }
  686. public void setPendingAreas(Vector areas) {
  687. pendingAreas = areas;
  688. }
  689. public void setPendingWidth(int width) {
  690. pendingWidth = width;
  691. }
  692. /**
  693. * sets hyphenation related traits: language, country, hyphenate, hyphenation-character
  694. * and minimum number of character to remain one the previous line and to be on the
  695. * next line.
  696. */
  697. public void changeHyphenation(HyphenationProps hyphProps) {
  698. this.hyphProps = hyphProps;
  699. }
  700. /**
  701. * creates a leader as String out of the given char and the leader length
  702. * and wraps it in an InlineArea which is returned
  703. */
  704. private InlineArea buildSimpleLeader(char c, int leaderLength) {
  705. int width = this.currentFontState.width(currentFontState.mapChar(c));
  706. if (width == 0) {
  707. MessageHandler.errorln("char " + c
  708. + " has width 0. Using width 100 instead.");
  709. width = 100;
  710. }
  711. int factor = (int)Math.floor(leaderLength / width);
  712. char[] leaderChars = new char[factor];
  713. for (int i = 0; i < factor; i++) {
  714. leaderChars[i] = c; // currentFontState.mapChar(c);
  715. }
  716. WordArea leaderPatternArea = new WordArea(currentFontState, this.red,
  717. this.green, this.blue,
  718. new String(leaderChars),
  719. leaderLength);
  720. leaderPatternArea.setYOffset(placementOffset);
  721. return leaderPatternArea;
  722. }
  723. /**
  724. * calculates the width of space which has to be inserted before the
  725. * start of the leader, so that all leader characters are aligned.
  726. * is used if property leader-align is set. At the moment only the value
  727. * for leader-align="reference-area" is supported.
  728. *
  729. */
  730. private int getLeaderAlignIndent(int leaderLength,
  731. int leaderPatternWidth) {
  732. // calculate position of used space in line area
  733. double position = getCurrentXPosition();
  734. // calculate factor of next leader pattern cycle
  735. double nextRepeatedLeaderPatternCycle = Math.ceil(position
  736. / leaderPatternWidth);
  737. // calculate difference between start of next leader
  738. // pattern cycle and already used space
  739. double difference =
  740. (leaderPatternWidth * nextRepeatedLeaderPatternCycle) - position;
  741. return (int)difference;
  742. }
  743. /**
  744. * calculates the used space in this line area
  745. */
  746. private int getCurrentXPosition() {
  747. return finalWidth + spaceWidth + startIndent + pendingWidth;
  748. }
  749. /**
  750. * extracts a complete word from the character data
  751. */
  752. private String getHyphenationWord(char[] characters, int wordStart) {
  753. boolean wordendFound = false;
  754. int counter = 0;
  755. char[] newWord = new char[characters.length]; // create a buffer
  756. while ((!wordendFound)
  757. && ((wordStart + counter) < characters.length)) {
  758. char tk = characters[wordStart + counter];
  759. if (Character.isLetter(tk)) {
  760. newWord[counter] = tk;
  761. counter++;
  762. } else {
  763. wordendFound = true;
  764. }
  765. }
  766. return new String(newWord, 0, counter);
  767. }
  768. /**
  769. * extracts word for hyphenation and calls hyphenation package,
  770. * handles cases of inword punctuation and quotation marks at the beginning
  771. * of words, but not in a internationalized way
  772. */
  773. public int doHyphenation(char[] characters, int position, int wordStart,
  774. int remainingWidth) {
  775. // check whether the language property has been set
  776. if (this.hyphProps.language.equalsIgnoreCase("none")) {
  777. MessageHandler.errorln("if property 'hyphenate' is used, a language must be specified");
  778. return wordStart;
  779. }
  780. /**
  781. * remaining part string of hyphenation
  782. */
  783. StringBuffer remainingString = new StringBuffer();
  784. /**
  785. * for words with some inword punctuation like / or -
  786. */
  787. StringBuffer preString = null;
  788. /**
  789. * char before the word, probably whitespace
  790. */
  791. char startChar = ' '; // characters[wordStart-1];
  792. /**
  793. * in word punctuation character
  794. */
  795. char inwordPunctuation;
  796. /**
  797. * the complete word handed to the hyphenator
  798. */
  799. String wordToHyphenate;
  800. // width of hyphenation character
  801. int hyphCharWidth =
  802. this.currentFontState.width(currentFontState.mapChar(this.hyphProps.hyphenationChar));
  803. remainingWidth -= hyphCharWidth;
  804. // handles ' or " at the beginning of the word
  805. if (characters[wordStart] == '"' || characters[wordStart] == '\'') {
  806. remainingString.append(characters[wordStart]);
  807. // extracts whole word from string
  808. wordToHyphenate = getHyphenationWord(characters, wordStart + 1);
  809. } else {
  810. wordToHyphenate = getHyphenationWord(characters, wordStart);
  811. }
  812. // if the extracted word is smaller than the remaining width
  813. // we have a non letter character inside the word. at the moment
  814. // we will only handle hard hyphens and slashes
  815. if (getWordWidth(wordToHyphenate) < remainingWidth) {
  816. inwordPunctuation =
  817. characters[wordStart + wordToHyphenate.length()];
  818. if (inwordPunctuation == '-' || inwordPunctuation == '/') {
  819. preString = new StringBuffer(wordToHyphenate);
  820. preString = preString.append(inwordPunctuation);
  821. wordToHyphenate =
  822. getHyphenationWord(characters,
  823. wordStart + wordToHyphenate.length()
  824. + 1);
  825. remainingWidth -=
  826. (getWordWidth(wordToHyphenate)
  827. + this.currentFontState.width(currentFontState.mapChar(inwordPunctuation)));
  828. }
  829. }
  830. // are there any hyphenation points
  831. Hyphenation hyph =
  832. Hyphenator.hyphenate(hyphProps.language, hyphProps.country,
  833. wordToHyphenate,
  834. hyphProps.hyphenationRemainCharacterCount,
  835. hyphProps.hyphenationPushCharacterCount);
  836. // no hyphenation points and no inword non letter character
  837. if (hyph == null && preString == null) {
  838. if (remainingString.length() > 0) {
  839. return wordStart - 1;
  840. } else {
  841. return wordStart;
  842. }
  843. // no hyphenation points, but a inword non-letter character
  844. } else if (hyph == null && preString != null) {
  845. remainingString.append(preString);
  846. // is.addMapWord(startChar,remainingString);
  847. this.addWord(startChar, remainingString);
  848. return wordStart + remainingString.length();
  849. // hyphenation points and no inword non-letter character
  850. } else if (hyph != null && preString == null) {
  851. int index = getFinalHyphenationPoint(hyph, remainingWidth);
  852. if (index != -1) {
  853. remainingString.append(hyph.getPreHyphenText(index));
  854. remainingString.append(this.hyphProps.hyphenationChar);
  855. // is.addMapWord(startChar,remainingString);
  856. this.addWord(startChar, remainingString);
  857. return wordStart + remainingString.length() - 1;
  858. }
  859. // hyphenation points and a inword non letter character
  860. } else if (hyph != null && preString != null) {
  861. int index = getFinalHyphenationPoint(hyph, remainingWidth);
  862. if (index != -1) {
  863. remainingString.append(preString.append(hyph.getPreHyphenText(index)));
  864. remainingString.append(this.hyphProps.hyphenationChar);
  865. // is.addMapWord(startChar,remainingString);
  866. this.addWord(startChar, remainingString);
  867. return wordStart + remainingString.length() - 1;
  868. } else {
  869. remainingString.append(preString);
  870. // is.addMapWord(startChar,remainingString);
  871. this.addWord(startChar, remainingString);
  872. return wordStart + remainingString.length();
  873. }
  874. }
  875. return wordStart;
  876. }
  877. /**
  878. * Calculates the wordWidth using the actual fontstate
  879. */
  880. private int getWordWidth(String word) {
  881. if (word == null)
  882. return 0;
  883. int wordLength = word.length();
  884. int width = 0;
  885. char[] characters = new char[wordLength];
  886. word.getChars(0, wordLength, characters, 0);
  887. for (int i = 0; i < wordLength; i++) {
  888. width += getCharWidth(characters[i]);
  889. }
  890. return width;
  891. }
  892. public int getRemainingWidth() {
  893. return this.getContentWidth() - this.getCurrentXPosition();
  894. }
  895. public void setLinkSet(LinkSet ls) {}
  896. public void addInlineArea(Area box) {
  897. addPending();
  898. addChild(box);
  899. prev = TEXT;
  900. finalWidth += box.getContentWidth();
  901. }
  902. public void addInlineSpace(InlineSpace is, int spaceWidth) {
  903. addChild(is);
  904. finalWidth += spaceWidth;
  905. // spaceWidth = 0;
  906. }
  907. /**
  908. * adds a single character to the line area tree
  909. */
  910. public int addCharacter(char data, LinkSet ls, boolean ul) {
  911. WordArea ia = null;
  912. int remainingWidth = this.getContentWidth()
  913. - this.getCurrentXPosition();
  914. int width =
  915. this.currentFontState.width(currentFontState.mapChar(data));
  916. // if it doesn't fit, return
  917. if (width > remainingWidth) {
  918. return org.apache.fop.fo.flow.Character.DOESNOT_FIT;
  919. } else {
  920. // if whitespace-collapse == true, discard character
  921. if (Character.isSpaceChar(data)
  922. && whiteSpaceCollapse == WhiteSpaceCollapse.TRUE) {
  923. return org.apache.fop.fo.flow.Character.OK;
  924. }
  925. // create new InlineArea
  926. ia = new WordArea(currentFontState, this.red, this.green,
  927. this.blue, new Character(data).toString(),
  928. width);
  929. ia.setYOffset(placementOffset);
  930. ia.setUnderlined(ul);
  931. pendingAreas.addElement(ia);
  932. if (Character.isSpaceChar(data)) {
  933. this.spaceWidth = +width;
  934. prev = LineArea.WHITESPACE;
  935. } else {
  936. pendingWidth += width;
  937. prev = LineArea.TEXT;
  938. }
  939. return org.apache.fop.fo.flow.Character.OK;
  940. }
  941. }
  942. /**
  943. * Same as addWord except that characters in wordBuf is mapped
  944. * to the current fontstate's encoding
  945. */
  946. private void addMapWord(char startChar, StringBuffer wordBuf) {
  947. StringBuffer mapBuf = new StringBuffer(wordBuf.length());
  948. for (int i = 0; i < wordBuf.length(); i++) {
  949. mapBuf.append(currentFontState.mapChar(wordBuf.charAt(i)));
  950. }
  951. addWord(startChar, mapBuf);
  952. }
  953. /**
  954. * adds a InlineArea containing the String startChar+wordBuf to the line area children.
  955. */
  956. private void addWord(char startChar, StringBuffer wordBuf) {
  957. String word = (wordBuf != null) ? wordBuf.toString() : "";
  958. WordArea hia;
  959. int startCharWidth = getCharWidth(startChar);
  960. if (isAnySpace(startChar)) {
  961. this.addChild(new InlineSpace(startCharWidth));
  962. } else {
  963. hia = new WordArea(currentFontState, this.red, this.green,
  964. this.blue,
  965. new Character(startChar).toString(), 1);
  966. hia.setYOffset(placementOffset);
  967. this.addChild(hia);
  968. }
  969. int wordWidth = this.getWordWidth(word);
  970. hia = new WordArea(currentFontState, this.red, this.green, this.blue,
  971. word, word.length());
  972. hia.setYOffset(placementOffset);
  973. this.addChild(hia);
  974. // calculate the space needed
  975. finalWidth += startCharWidth + wordWidth;
  976. }
  977. /**
  978. * extracts from a hyphenated word the best (most greedy) fit
  979. */
  980. private int getFinalHyphenationPoint(Hyphenation hyph,
  981. int remainingWidth) {
  982. int[] hyphenationPoints = hyph.getHyphenationPoints();
  983. int numberOfHyphenationPoints = hyphenationPoints.length;
  984. int index = -1;
  985. String wordBegin = "";
  986. int wordBeginWidth = 0;
  987. for (int i = 0; i < numberOfHyphenationPoints; i++) {
  988. wordBegin = hyph.getPreHyphenText(i);
  989. if (this.getWordWidth(wordBegin) > remainingWidth) {
  990. break;
  991. }
  992. index = i;
  993. }
  994. return index;
  995. }
  996. /**
  997. * Checks if it's legal to break a word in the middle
  998. * based on the current language property.
  999. * @return true if legal to break word in the middle
  1000. */
  1001. private boolean canBreakMidWord() {
  1002. boolean ret = false;
  1003. if (hyphProps != null && hyphProps.language != null
  1004. &&!hyphProps.language.equals("NONE")) {
  1005. String lang = hyphProps.language.toLowerCase();
  1006. if ("zh".equals(lang) || "ja".equals(lang) || "ko".equals(lang)
  1007. || "vi".equals(lang))
  1008. ret = true;
  1009. }
  1010. return ret;
  1011. }
  1012. /**
  1013. * Helper method for getting the width of a unicode char
  1014. * from the current fontstate.
  1015. * This also performs some guessing on widths on various
  1016. * versions of space that might not exists in the font.
  1017. */
  1018. private int getCharWidth(char c) {
  1019. int width = currentFontState.width(currentFontState.mapChar(c));
  1020. if (width <= 0) {
  1021. // Estimate the width of spaces not represented in
  1022. // the font
  1023. int em = currentFontState.width(currentFontState.mapChar('m'));
  1024. int en = currentFontState.width(currentFontState.mapChar('n'));
  1025. if (em <= 0)
  1026. em = 500 * currentFontState.getFontSize();
  1027. if (en <= 0)
  1028. en = em - 10;
  1029. if (c == ' ')
  1030. width = em;
  1031. if (c == '\u2000')
  1032. width = en;
  1033. if (c == '\u2001')
  1034. width = em;
  1035. if (c == '\u2002')
  1036. width = em / 2;
  1037. if (c == '\u2003')
  1038. width = currentFontState.getFontSize();
  1039. if (c == '\u2004')
  1040. width = em / 3;
  1041. if (c == '\u2005')
  1042. width = em / 4;
  1043. if (c == '\u2006')
  1044. width = em / 6;
  1045. if (c == '\u2007')
  1046. width = getCharWidth(' ');
  1047. if (c == '\u2008')
  1048. width = getCharWidth('.');
  1049. if (c == '\u2009')
  1050. width = em / 5;
  1051. if (c == '\u200A')
  1052. width = 5;
  1053. if (c == '\u200B')
  1054. width = 100;
  1055. if (c == '\u00A0')
  1056. width = getCharWidth(' ');
  1057. if (c == '\u202F')
  1058. width = getCharWidth(' ') / 2;
  1059. if (c == '\u3000')
  1060. width = getCharWidth(' ') * 2;
  1061. if ((c == '\n') || (c == '\r') || (c == '\t'))
  1062. width = getCharWidth(' ');
  1063. }
  1064. return width;
  1065. }
  1066. /**
  1067. * Helper method to determine if the character is a
  1068. * space with normal behaviour. Normal behaviour means that
  1069. * it's not non-breaking
  1070. */
  1071. private boolean isSpace(char c) {
  1072. if (c == ' ' || c == '\u2000' || // en quad
  1073. c == '\u2001' || // em quad
  1074. c == '\u2002' || // en space
  1075. c == '\u2003' || // em space
  1076. c == '\u2004' || // three-per-em space
  1077. c == '\u2005' || // four--per-em space
  1078. c == '\u2006' || // six-per-em space
  1079. c == '\u2007' || // figure space
  1080. c == '\u2008' || // punctuation space
  1081. c == '\u2009' || // thin space
  1082. c == '\u200A' || // hair space
  1083. c == '\u200B') // zero width space
  1084. return true;
  1085. else
  1086. return false;
  1087. }
  1088. /**
  1089. * Method to determine if the character is a nonbreaking
  1090. * space.
  1091. */
  1092. private boolean isNBSP(char c) {
  1093. if (c == '\u00A0' || c == '\u202F' || // narrow no-break space
  1094. c == '\u3000' || // ideographic space
  1095. c == '\uFEFF') { // zero width no-break space
  1096. return true;
  1097. } else
  1098. return false;
  1099. }
  1100. /**
  1101. * @return true if the character represents any kind of space
  1102. */
  1103. private boolean isAnySpace(char c) {
  1104. boolean ret = (isSpace(c) || isNBSP(c));
  1105. return ret;
  1106. }
  1107. /**
  1108. * Add a word that might contain non-breaking spaces.
  1109. * Split the word into WordArea and InlineSpace and add it.
  1110. * If addToPending is true, add to pending areas.
  1111. */
  1112. private void addSpacedWord(String word, LinkSet ls, int startw,
  1113. int spacew, TextState textState,
  1114. boolean addToPending) {
  1115. StringTokenizer st = new StringTokenizer(word, "\u00A0\u202F\u3000\uFEFF", true);
  1116. int extraw = 0;
  1117. while (st.hasMoreTokens()) {
  1118. String currentWord = st.nextToken();
  1119. if (currentWord.length() == 1
  1120. && (isNBSP(currentWord.charAt(0)))) {
  1121. // Add an InlineSpace
  1122. int spaceWidth = getCharWidth(currentWord.charAt(0));
  1123. if (spaceWidth > 0) {
  1124. InlineSpace is = new InlineSpace(spaceWidth);
  1125. extraw += spaceWidth;
  1126. if (prevUlState) {
  1127. is.setUnderlined(textState.getUnderlined());
  1128. }
  1129. if (prevOlState) {
  1130. is.setOverlined(textState.getOverlined());
  1131. }
  1132. if (prevLTState) {
  1133. is.setLineThrough(textState.getLineThrough());
  1134. }
  1135. if (addToPending) {
  1136. pendingAreas.addElement(is);
  1137. pendingWidth += spaceWidth;
  1138. } else {
  1139. addChild(is);
  1140. }
  1141. }
  1142. } else {
  1143. WordArea ia = new WordArea(currentFontState, this.red,
  1144. this.green, this.blue,
  1145. currentWord,
  1146. getWordWidth(currentWord));
  1147. ia.setYOffset(placementOffset);
  1148. ia.setUnderlined(textState.getUnderlined());
  1149. prevUlState = textState.getUnderlined();
  1150. ia.setOverlined(textState.getOverlined());
  1151. prevOlState = textState.getOverlined();
  1152. ia.setLineThrough(textState.getLineThrough());
  1153. prevLTState = textState.getLineThrough();
  1154. ia.setVerticalAlign(vAlign);
  1155. if (addToPending) {
  1156. pendingAreas.addElement(ia);
  1157. pendingWidth += getWordWidth(currentWord);
  1158. } else {
  1159. addChild(ia);
  1160. }
  1161. if (ls != null) {
  1162. Rectangle lr = new Rectangle(startw + extraw, spacew,
  1163. ia.getContentWidth(),
  1164. fontState.getFontSize());
  1165. ls.addRect(lr, this, ia);
  1166. }
  1167. }
  1168. }
  1169. }
  1170. }