Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

BorderPainter.java 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.render.intermediate;
  19. import java.awt.Color;
  20. import java.awt.Rectangle;
  21. import java.io.IOException;
  22. import org.apache.fop.traits.BorderProps;
  23. /**
  24. * This is an abstract base class for handling border painting.
  25. */
  26. public class BorderPainter {
  27. // TODO Use a class to model border instead of an array
  28. /** Convention index of before top */
  29. protected static final int TOP = 0;
  30. /** Convention index of right border */
  31. protected static final int RIGHT = 1;
  32. /** Convention index of bottom border */
  33. protected static final int BOTTOM = 2;
  34. /** Convention index of left border */
  35. protected static final int LEFT = 3;
  36. // TODO Use a class to model border corners instead of an array
  37. /** Convention index of top-left border corners */
  38. protected static final int TOP_LEFT = 0;
  39. /** Convention index of top-right-end border corners */
  40. protected static final int TOP_RIGHT = 1;
  41. /** Convention index of bottom-right border corners */
  42. protected static final int BOTTOM_RIGHT = 2;
  43. /** Convention index of bottom-left border corners */
  44. protected static final int BOTTOM_LEFT = 3;
  45. /** The ratio between a solid dash and the white-space in a dashed-border */
  46. public static final float DASHED_BORDER_SPACE_RATIO = 0.5f;
  47. /** The length of the dash as a factor of the border width i.e. 2 -> dashWidth = 2*borderWidth */
  48. protected static final float DASHED_BORDER_LENGTH_FACTOR = 2.0f;
  49. private final GraphicsPainter graphicsPainter;
  50. public BorderPainter(GraphicsPainter graphicsPainter) {
  51. this.graphicsPainter = graphicsPainter;
  52. }
  53. /**
  54. * Draws borders.
  55. * @param borderRect the border rectangle
  56. * @param bpsTop the border specification on the top side
  57. * @param bpsBottom the border specification on the bottom side
  58. * @param bpsLeft the border specification on the left side
  59. * @param bpsRight the border specification on the end side
  60. * @param innerBackgroundColor the inner background color
  61. * @throws IFException if an error occurs while drawing the borders
  62. */
  63. public void drawBorders(Rectangle borderRect,
  64. BorderProps bpsTop, BorderProps bpsBottom,
  65. BorderProps bpsLeft, BorderProps bpsRight, Color innerBackgroundColor)
  66. throws IFException {
  67. try {
  68. drawRoundedBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight);
  69. } catch (IOException ioe) {
  70. throw new IFException("IO error drawing borders", ioe);
  71. }
  72. }
  73. private BorderProps sanitizeBorderProps(BorderProps bps) {
  74. return bps == null ? bps : bps.width == 0 ? (BorderProps) null : bps;
  75. }
  76. /**
  77. * TODO merge with drawRoundedBorders()?
  78. * @param borderRect the border rectangle
  79. * @param bpsTop the border specification on the top side
  80. * @param bpsBottom the border specification on the bottom side
  81. * @param bpsLeft the border specification on the left side
  82. * @param bpsRight the border specification on the end side
  83. * @throws IOException
  84. */
  85. protected void drawRectangularBorders(Rectangle borderRect,
  86. BorderProps bpsTop, BorderProps bpsBottom,
  87. BorderProps bpsLeft, BorderProps bpsRight) throws IOException {
  88. bpsTop = sanitizeBorderProps(bpsTop);
  89. bpsBottom = sanitizeBorderProps(bpsBottom);
  90. bpsLeft = sanitizeBorderProps(bpsLeft);
  91. bpsRight = sanitizeBorderProps(bpsRight);
  92. int startx = borderRect.x;
  93. int starty = borderRect.y;
  94. int width = borderRect.width;
  95. int height = borderRect.height;
  96. boolean[] b = new boolean[] {
  97. (bpsTop != null), (bpsRight != null),
  98. (bpsBottom != null), (bpsLeft != null)};
  99. if (!b[TOP] && !b[RIGHT] && !b[BOTTOM] && !b[LEFT]) {
  100. return;
  101. }
  102. int[] bw = new int[] {
  103. (b[TOP] ? bpsTop.width : 0),
  104. (b[RIGHT] ? bpsRight.width : 0),
  105. (b[BOTTOM] ? bpsBottom.width : 0),
  106. (b[LEFT] ? bpsLeft.width : 0)};
  107. int[] clipw = new int[] {
  108. BorderProps.getClippedWidth(bpsTop),
  109. BorderProps.getClippedWidth(bpsRight),
  110. BorderProps.getClippedWidth(bpsBottom),
  111. BorderProps.getClippedWidth(bpsLeft)};
  112. starty += clipw[TOP];
  113. height -= clipw[TOP];
  114. height -= clipw[BOTTOM];
  115. startx += clipw[LEFT];
  116. width -= clipw[LEFT];
  117. width -= clipw[RIGHT];
  118. boolean[] slant = new boolean[] {
  119. (b[LEFT] && b[TOP]),
  120. (b[TOP] && b[RIGHT]),
  121. (b[RIGHT] && b[BOTTOM]),
  122. (b[BOTTOM] && b[LEFT])};
  123. if (bpsTop != null) {
  124. int sx1 = startx;
  125. int sx2 = (slant[TOP_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1);
  126. int ex1 = startx + width;
  127. int ex2 = (slant[TOP_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1);
  128. int outery = starty - clipw[TOP];
  129. int clipy = outery + clipw[TOP];
  130. int innery = outery + bw[TOP];
  131. saveGraphicsState();
  132. moveTo(sx1, clipy);
  133. int sx1a = sx1;
  134. int ex1a = ex1;
  135. if (isCollapseOuter(bpsTop)) {
  136. if (isCollapseOuter(bpsLeft)) {
  137. sx1a -= clipw[LEFT];
  138. }
  139. if (isCollapseOuter(bpsRight)) {
  140. ex1a += clipw[RIGHT];
  141. }
  142. lineTo(sx1a, outery);
  143. lineTo(ex1a, outery);
  144. }
  145. lineTo(ex1, clipy);
  146. lineTo(ex2, innery);
  147. lineTo(sx2, innery);
  148. closePath();
  149. clip();
  150. drawBorderLine(sx1a, outery, ex1a, innery, true, true,
  151. bpsTop.style, bpsTop.color);
  152. restoreGraphicsState();
  153. }
  154. if (bpsRight != null) {
  155. int sy1 = starty;
  156. int sy2 = (slant[TOP_RIGHT] ? sy1 + bw[TOP] - clipw[TOP] : sy1);
  157. int ey1 = starty + height;
  158. int ey2 = (slant[BOTTOM_RIGHT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1);
  159. int outerx = startx + width + clipw[RIGHT];
  160. int clipx = outerx - clipw[RIGHT];
  161. int innerx = outerx - bw[RIGHT];
  162. saveGraphicsState();
  163. moveTo(clipx, sy1);
  164. int sy1a = sy1;
  165. int ey1a = ey1;
  166. if (isCollapseOuter(bpsRight)) {
  167. if (isCollapseOuter(bpsTop)) {
  168. sy1a -= clipw[TOP];
  169. }
  170. if (isCollapseOuter(bpsBottom)) {
  171. ey1a += clipw[BOTTOM];
  172. }
  173. lineTo(outerx, sy1a);
  174. lineTo(outerx, ey1a);
  175. }
  176. lineTo(clipx, ey1);
  177. lineTo(innerx, ey2);
  178. lineTo(innerx, sy2);
  179. closePath();
  180. clip();
  181. drawBorderLine(innerx, sy1a, outerx, ey1a, false, false,
  182. bpsRight.style, bpsRight.color);
  183. restoreGraphicsState();
  184. }
  185. if (bpsBottom != null) {
  186. int sx1 = startx;
  187. int sx2 = (slant[BOTTOM_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1);
  188. int ex1 = startx + width;
  189. int ex2 = (slant[BOTTOM_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1);
  190. int outery = starty + height + clipw[BOTTOM];
  191. int clipy = outery - clipw[BOTTOM];
  192. int innery = outery - bw[BOTTOM];
  193. saveGraphicsState();
  194. moveTo(ex1, clipy);
  195. int sx1a = sx1;
  196. int ex1a = ex1;
  197. if (isCollapseOuter(bpsBottom)) {
  198. if (isCollapseOuter(bpsLeft)) {
  199. sx1a -= clipw[LEFT];
  200. }
  201. if (isCollapseOuter(bpsRight)) {
  202. ex1a += clipw[RIGHT];
  203. }
  204. lineTo(ex1a, outery);
  205. lineTo(sx1a, outery);
  206. }
  207. lineTo(sx1, clipy);
  208. lineTo(sx2, innery);
  209. lineTo(ex2, innery);
  210. closePath();
  211. clip();
  212. drawBorderLine(sx1a, innery, ex1a, outery, true, false,
  213. bpsBottom.style, bpsBottom.color);
  214. restoreGraphicsState();
  215. }
  216. if (bpsLeft != null) {
  217. int sy1 = starty;
  218. int sy2 = (slant[TOP_LEFT] ? sy1 + bw[TOP] - clipw[TOP] : sy1);
  219. int ey1 = sy1 + height;
  220. int ey2 = (slant[BOTTOM_LEFT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1);
  221. int outerx = startx - clipw[LEFT];
  222. int clipx = outerx + clipw[LEFT];
  223. int innerx = outerx + bw[LEFT];
  224. saveGraphicsState();
  225. moveTo(clipx, ey1);
  226. int sy1a = sy1;
  227. int ey1a = ey1;
  228. if (isCollapseOuter(bpsLeft)) {
  229. if (isCollapseOuter(bpsTop)) {
  230. sy1a -= clipw[TOP];
  231. }
  232. if (isCollapseOuter(bpsBottom)) {
  233. ey1a += clipw[BOTTOM];
  234. }
  235. lineTo(outerx, ey1a);
  236. lineTo(outerx, sy1a);
  237. }
  238. lineTo(clipx, sy1);
  239. lineTo(innerx, sy2);
  240. lineTo(innerx, ey2);
  241. closePath();
  242. clip();
  243. drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsLeft.style, bpsLeft.color);
  244. restoreGraphicsState();
  245. }
  246. }
  247. private boolean isCollapseOuter(BorderProps bp) {
  248. return bp != null && bp.isCollapseOuter();
  249. }
  250. /**
  251. * This method calculates the length of the "dash" in a dashed border. The dash satisfies the
  252. * condition that corners start on a dash and end with a dash (rather than ending with a white space).
  253. * @param borderLength The length of the border.
  254. * @param borderWidth The width/thickness of the border.
  255. * @return returns the length of the dash such that it fits the criteria above.
  256. */
  257. public static float dashWidthCalculator(float borderLength, float borderWidth) {
  258. float dashWidth = DASHED_BORDER_LENGTH_FACTOR * borderWidth;
  259. if (borderWidth < 3) {
  260. dashWidth = (DASHED_BORDER_LENGTH_FACTOR * 3) * borderWidth;
  261. }
  262. int period = (int) ((borderLength - dashWidth) / dashWidth / (1.0f + DASHED_BORDER_SPACE_RATIO));
  263. period = period < 0 ? 0 : period;
  264. return borderLength / (period * (1.0f + DASHED_BORDER_SPACE_RATIO) + 1.0f);
  265. }
  266. /** TODO merge with drawRectangularBorders?
  267. * @param borderRect the border rectangle
  268. * @throws IOException on io exception
  269. * */
  270. protected void drawRoundedBorders(Rectangle borderRect,
  271. BorderProps beforeBorderProps, BorderProps afterBorderProps,
  272. BorderProps startBorderProps, BorderProps endBorderProps) throws IOException {
  273. BorderSegment before = borderSegmentForBefore(beforeBorderProps);
  274. BorderSegment after = borderSegmentForAfter(afterBorderProps);
  275. BorderSegment start = borderSegmentForStart(startBorderProps);
  276. BorderSegment end = borderSegmentForEnd(endBorderProps);
  277. if (before.getWidth() == 0 && after.getWidth() == 0 && start.getWidth() == 0 && end.getWidth() == 0) {
  278. return;
  279. }
  280. final int startx = borderRect.x + start.getClippedWidth();
  281. final int starty = borderRect.y + before.getClippedWidth();
  282. final int width = borderRect.width - start.getClippedWidth() - end.getClippedWidth();
  283. final int height = borderRect.height - before.getClippedWidth() - after.getClippedWidth();
  284. //Determine scale factor if any adjacent elliptic corners overlap
  285. double cornerCorrectionFactor = calculateCornerScaleCorrection(width, height, before, after, start,
  286. end);
  287. drawBorderSegment(start, before, end, 0, width, startx, starty, cornerCorrectionFactor);
  288. drawBorderSegment(before, end, after, 1, height, startx + width, starty, cornerCorrectionFactor);
  289. drawBorderSegment(end, after, start, 2, width, startx + width, starty + height,
  290. cornerCorrectionFactor);
  291. drawBorderSegment(after, start, before, 3, height, startx, starty + height, cornerCorrectionFactor);
  292. }
  293. private void drawBorderSegment(BorderSegment start, BorderSegment before, BorderSegment end,
  294. int orientation, int width, int x, int y, double cornerCorrectionFactor) throws IOException {
  295. if (before.getWidth() != 0) {
  296. //Let x increase in the START->END direction
  297. final int sx2 = start.getWidth() - start.getClippedWidth();
  298. final int ex1 = width;
  299. final int ex2 = ex1 - end.getWidth() + end.getClippedWidth();
  300. final int outery = -before.getClippedWidth();
  301. final int innery = outery + before.getWidth();
  302. final int ellipseSBRadiusX = correctRadius(cornerCorrectionFactor, start.getRadiusEnd());
  303. final int ellipseSBRadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusStart());
  304. final int ellipseBERadiusX = correctRadius(cornerCorrectionFactor, end.getRadiusStart());
  305. final int ellipseBERadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusEnd());
  306. saveGraphicsState();
  307. translateCoordinates(x, y);
  308. if (orientation != 0) {
  309. rotateCoordinates(Math.PI * orientation / 2d);
  310. }
  311. final int ellipseSBX = ellipseSBRadiusX;
  312. final int ellipseSBY = ellipseSBRadiusY;
  313. final int ellipseBEX = ex1 - ellipseBERadiusX;
  314. final int ellipseBEY = ellipseBERadiusY;
  315. int sx1a = 0;
  316. int ex1a = ex1;
  317. if (ellipseSBRadiusX != 0 && ellipseSBRadiusY != 0) {
  318. final double[] joinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX,
  319. ellipseSBRadiusY, sx2, innery);
  320. final double outerJoinPointX = joinMetrics[0];
  321. final double outerJoinPointY = joinMetrics[1];
  322. final double sbJoinAngle = joinMetrics[2];
  323. moveTo((int) outerJoinPointX, (int) outerJoinPointY);
  324. arcTo(Math.PI + sbJoinAngle, Math.PI * 3 / 2,
  325. ellipseSBX, ellipseSBY, ellipseSBRadiusX, ellipseSBRadiusY);
  326. } else {
  327. moveTo(0, 0);
  328. if (before.isCollapseOuter()) {
  329. if (start.isCollapseOuter()) {
  330. sx1a -= start.getClippedWidth();
  331. }
  332. if (end.isCollapseOuter()) {
  333. ex1a += end.getClippedWidth();
  334. }
  335. lineTo(sx1a, outery);
  336. lineTo(ex1a, outery);
  337. }
  338. }
  339. if (ellipseBERadiusX != 0 && ellipseBERadiusY != 0) {
  340. final double[] outerJoinMetrics = getCornerBorderJoinMetrics(
  341. ellipseBERadiusX, ellipseBERadiusY, ex1 - ex2, innery);
  342. final double beJoinAngle = ex1 == ex2 ? Math.PI / 2 : Math.PI / 2 - outerJoinMetrics[2];
  343. lineTo(ellipseBEX, 0);
  344. arcTo(Math.PI * 3 / 2 , Math.PI * 3 / 2 + beJoinAngle,
  345. ellipseBEX, ellipseBEY, ellipseBERadiusX, ellipseBERadiusY);
  346. if (ellipseBEX < ex2 && ellipseBEY > innery) {
  347. final double[] innerJoinMetrics = getCornerBorderJoinMetrics(
  348. (double) ex2 - ellipseBEX, (double) ellipseBEY - innery, ex1 - ex2, innery);
  349. final double innerJoinPointX = innerJoinMetrics[0];
  350. final double innerJoinPointY = innerJoinMetrics[1];
  351. final double beInnerJoinAngle = Math.PI / 2 - innerJoinMetrics[2];
  352. lineTo((int) (ex2 - innerJoinPointX), (int) (innerJoinPointY + innery));
  353. arcTo(beInnerJoinAngle + Math.PI * 3 / 2, Math.PI * 3 / 2,
  354. ellipseBEX, ellipseBEY, ex2 - ellipseBEX, ellipseBEY - innery);
  355. } else {
  356. lineTo(ex2, innery);
  357. }
  358. } else {
  359. lineTo(ex1, 0);
  360. lineTo(ex2, innery);
  361. }
  362. if (ellipseSBRadiusX == 0) {
  363. lineTo(sx2, innery);
  364. } else {
  365. if (ellipseSBX > sx2 && ellipseSBY > innery) {
  366. final double[] innerJoinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX - sx2,
  367. ellipseSBRadiusY - innery, sx2, innery);
  368. final double sbInnerJoinAngle = innerJoinMetrics[2];
  369. lineTo(ellipseSBX, innery);
  370. arcTo(Math.PI * 3 / 2, sbInnerJoinAngle + Math.PI,
  371. ellipseSBX, ellipseSBY, ellipseSBX - sx2, ellipseSBY - innery);
  372. } else {
  373. lineTo(sx2, innery);
  374. }
  375. }
  376. closePath();
  377. clip();
  378. if (ellipseBERadiusY == 0 && ellipseSBRadiusY == 0) {
  379. drawBorderLine(sx1a, outery, ex1a, innery, true, true,
  380. before.getStyle(), before.getColor());
  381. } else {
  382. int innerFillY = Math.max(Math.max(ellipseBEY, ellipseSBY), innery);
  383. drawBorderLine(sx1a, outery, ex1a, innerFillY, true, true,
  384. before.getStyle(), before.getColor());
  385. }
  386. restoreGraphicsState();
  387. }
  388. }
  389. private static int correctRadius(double cornerCorrectionFactor, int radius) {
  390. return (int) (Math.round(cornerCorrectionFactor * radius));
  391. }
  392. private static BorderSegment borderSegmentForBefore(BorderProps before) {
  393. return AbstractBorderSegment.asBorderSegment(before);
  394. }
  395. private static BorderSegment borderSegmentForAfter(BorderProps after) {
  396. return AbstractBorderSegment.asFlippedBorderSegment(after);
  397. }
  398. private static BorderSegment borderSegmentForStart(BorderProps start) {
  399. return AbstractBorderSegment.asFlippedBorderSegment(start);
  400. }
  401. private static BorderSegment borderSegmentForEnd(BorderProps end) {
  402. return AbstractBorderSegment.asBorderSegment(end);
  403. }
  404. private interface BorderSegment {
  405. Color getColor();
  406. int getStyle();
  407. int getWidth();
  408. int getClippedWidth();
  409. int getRadiusStart();
  410. int getRadiusEnd();
  411. boolean isCollapseOuter();
  412. boolean isSpecified();
  413. }
  414. private abstract static class AbstractBorderSegment implements BorderSegment {
  415. private static BorderSegment asBorderSegment(BorderProps borderProps) {
  416. return borderProps == null ? NullBorderSegment.INSTANCE : new WrappingBorderSegment(borderProps);
  417. }
  418. private static BorderSegment asFlippedBorderSegment(BorderProps borderProps) {
  419. return borderProps == null ? NullBorderSegment.INSTANCE : new FlippedBorderSegment(borderProps);
  420. }
  421. public boolean isSpecified() {
  422. return !(this instanceof NullBorderSegment);
  423. }
  424. private static class WrappingBorderSegment extends AbstractBorderSegment {
  425. protected final BorderProps borderProps;
  426. private final int clippedWidth;
  427. WrappingBorderSegment(BorderProps borderProps) {
  428. this.borderProps = borderProps;
  429. clippedWidth = BorderProps.getClippedWidth(borderProps);
  430. }
  431. public int getStyle() {
  432. return borderProps.style;
  433. }
  434. public Color getColor() {
  435. return borderProps.color;
  436. }
  437. public int getWidth() {
  438. return borderProps.width;
  439. }
  440. public int getClippedWidth() {
  441. return clippedWidth;
  442. }
  443. public boolean isCollapseOuter() {
  444. return borderProps.isCollapseOuter();
  445. }
  446. public int getRadiusStart() {
  447. return borderProps.getRadiusStart();
  448. }
  449. public int getRadiusEnd() {
  450. return borderProps.getRadiusEnd();
  451. }
  452. }
  453. private static class FlippedBorderSegment extends WrappingBorderSegment {
  454. FlippedBorderSegment(BorderProps borderProps) {
  455. super(borderProps);
  456. }
  457. public int getRadiusStart() {
  458. return borderProps.getRadiusEnd();
  459. }
  460. public int getRadiusEnd() {
  461. return borderProps.getRadiusStart();
  462. }
  463. }
  464. private static final class NullBorderSegment extends AbstractBorderSegment {
  465. public static final NullBorderSegment INSTANCE = new NullBorderSegment();
  466. private NullBorderSegment() {
  467. }
  468. public int getWidth() {
  469. return 0;
  470. }
  471. public int getClippedWidth() {
  472. return 0;
  473. }
  474. public int getRadiusStart() {
  475. return 0;
  476. }
  477. public int getRadiusEnd() {
  478. return 0;
  479. }
  480. public boolean isCollapseOuter() {
  481. return false;
  482. }
  483. public Color getColor() {
  484. throw new UnsupportedOperationException();
  485. }
  486. public int getStyle() {
  487. throw new UnsupportedOperationException();
  488. }
  489. public boolean isSpecified() {
  490. return false;
  491. }
  492. }
  493. }
  494. private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, double xWidth,
  495. double yWidth) {
  496. if (xWidth > 0) {
  497. return getCornerBorderJoinMetrics(ellipseCenterX, ellipseCenterY, yWidth / xWidth);
  498. } else {
  499. return new double[]{0, ellipseCenterY, 0};
  500. }
  501. }
  502. private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY,
  503. double borderWidthRatio) {
  504. double x = ellipseCenterY * ellipseCenterX * (
  505. ellipseCenterY + ellipseCenterX * borderWidthRatio
  506. - Math.sqrt(2d * ellipseCenterX * ellipseCenterY * borderWidthRatio)
  507. ) / (ellipseCenterY * ellipseCenterY
  508. + ellipseCenterX * ellipseCenterX * borderWidthRatio * borderWidthRatio);
  509. double y = borderWidthRatio * x;
  510. return new double[]{x, y, Math.atan((ellipseCenterY - y) / (ellipseCenterX - x))};
  511. }
  512. /**
  513. * Clip the background to the inner border
  514. * @param rect clipping rectangle
  515. * @param bpsBefore before border
  516. * @param bpsAfter after border
  517. * @param bpsStart start border
  518. * @param bpsEnd end border
  519. * @throws IOException if an I/O error occurs
  520. */
  521. public void clipBackground(Rectangle rect,
  522. BorderProps bpsBefore, BorderProps bpsAfter,
  523. BorderProps bpsStart, BorderProps bpsEnd) throws IOException {
  524. BorderSegment before = borderSegmentForBefore(bpsBefore);
  525. BorderSegment after = borderSegmentForAfter(bpsAfter);
  526. BorderSegment start = borderSegmentForStart(bpsStart);
  527. BorderSegment end = borderSegmentForEnd(bpsEnd);
  528. int startx = rect.x;
  529. int starty = rect.y;
  530. int width = rect.width;
  531. int height = rect.height;
  532. double correctionFactor = calculateCornerCorrectionFactor(width + start.getWidth() + end.getWidth(),
  533. height + before.getWidth() + after.getWidth(), bpsBefore, bpsAfter, bpsStart, bpsEnd);
  534. Corner cornerBeforeEnd = Corner.createBeforeEndCorner(before, end, correctionFactor);
  535. Corner cornerEndAfter = Corner.createEndAfterCorner(end, after, correctionFactor);
  536. Corner cornerAfterStart = Corner.createAfterStartCorner(after, start, correctionFactor);
  537. Corner cornerStartBefore = Corner.createStartBeforeCorner(start, before, correctionFactor);
  538. new PathPainter(startx + cornerStartBefore.radiusX, starty)
  539. .lineHorizTo(width - cornerStartBefore.radiusX - cornerBeforeEnd.radiusX)
  540. .drawCorner(cornerBeforeEnd)
  541. .lineVertTo(height - cornerBeforeEnd.radiusY - cornerEndAfter.radiusY)
  542. .drawCorner(cornerEndAfter)
  543. .lineHorizTo(cornerEndAfter.radiusX + cornerAfterStart.radiusX - width)
  544. .drawCorner(cornerAfterStart)
  545. .lineVertTo(cornerAfterStart.radiusY + cornerStartBefore.radiusY - height)
  546. .drawCorner(cornerStartBefore);
  547. clip();
  548. }
  549. /**
  550. * The four corners
  551. * SB - Start-Before
  552. * BE - Before-End
  553. * EA - End-After
  554. * AS - After-Start
  555. *
  556. * 0 --> x
  557. * |
  558. * v
  559. * y
  560. *
  561. * SB BE
  562. * *----*
  563. * | |
  564. * | |
  565. * *----*
  566. * AS EA
  567. *
  568. */
  569. private enum CornerAngles {
  570. /** The before-end angles */
  571. BEFORE_END(Math.PI * 3 / 2, 0),
  572. /** The end-after angles */
  573. END_AFTER(0, Math.PI / 2),
  574. /** The after-start angles*/
  575. AFTER_START(Math.PI / 2, Math.PI),
  576. /** The start-before angles */
  577. START_BEFORE(Math.PI, Math.PI * 3 / 2);
  578. /** Angle of the start of the corner arch relative to the x-axis in the counter-clockwise direction */
  579. private final double start;
  580. /** Angle of the end of the corner arch relative to the x-axis in the counter-clockwise direction */
  581. private final double end;
  582. CornerAngles(double start, double end) {
  583. this.start = start;
  584. this.end = end;
  585. }
  586. }
  587. private static final class Corner {
  588. private static final Corner SQUARE = new Corner(0, 0, null, 0, 0, 0, 0);
  589. /** The radius of the elliptic corner in the x direction */
  590. private final int radiusX;
  591. /** The radius of the elliptic corner in the y direction */
  592. private final int radiusY;
  593. /** The start and end angles of the corner ellipse */
  594. private final CornerAngles angles;
  595. /** The offset in the x direction of the center of the ellipse relative to the starting point */
  596. private final int centerX;
  597. /** The offset in the y direction of the center of the ellipse relative to the starting point */
  598. private final int centerY;
  599. /** The value in the x direction that the corner extends relative to the starting point */
  600. private final int incrementX;
  601. /** The value in the y direction that the corner extends relative to the starting point */
  602. private final int incrementY;
  603. private Corner(int radiusX, int radiusY, CornerAngles angles, int ellipseOffsetX,
  604. int ellipseOffsetY, int incrementX, int incrementY) {
  605. this.radiusX = radiusX;
  606. this.radiusY = radiusY;
  607. this.angles = angles;
  608. this.centerX = ellipseOffsetX;
  609. this.centerY = ellipseOffsetY;
  610. this.incrementX = incrementX;
  611. this.incrementY = incrementY;
  612. }
  613. private static int extentFromRadiusStart(BorderSegment border, double correctionFactor) {
  614. return extentFromRadius(border.getRadiusStart(), border, correctionFactor);
  615. }
  616. private static int extentFromRadiusEnd(BorderSegment border, double correctionFactor) {
  617. return extentFromRadius(border.getRadiusEnd(), border, correctionFactor);
  618. }
  619. private static int extentFromRadius(int radius, BorderSegment border, double correctionFactor) {
  620. return Math.max((int) (radius * correctionFactor) - border.getWidth(), 0);
  621. }
  622. public static Corner createBeforeEndCorner(BorderSegment before, BorderSegment end,
  623. double correctionFactor) {
  624. int width = end.getRadiusStart();
  625. int height = before.getRadiusEnd();
  626. if (width == 0 || height == 0) {
  627. return SQUARE;
  628. }
  629. int x = extentFromRadiusStart(end, correctionFactor);
  630. int y = extentFromRadiusEnd(before, correctionFactor);
  631. return new Corner(x, y, CornerAngles.BEFORE_END, 0, y, x, y);
  632. }
  633. public static Corner createEndAfterCorner(BorderSegment end, BorderSegment after,
  634. double correctionFactor) {
  635. int width = end.getRadiusEnd();
  636. int height = after.getRadiusStart();
  637. if (width == 0 || height == 0) {
  638. return SQUARE;
  639. }
  640. int x = extentFromRadiusEnd(end, correctionFactor);
  641. int y = extentFromRadiusStart(after, correctionFactor);
  642. return new Corner(x, y, CornerAngles.END_AFTER, -x, 0, -x, y);
  643. }
  644. public static Corner createAfterStartCorner(BorderSegment after, BorderSegment start,
  645. double correctionFactor) {
  646. int width = start.getRadiusStart();
  647. int height = after.getRadiusEnd();
  648. if (width == 0 || height == 0) {
  649. return SQUARE;
  650. }
  651. int x = extentFromRadiusStart(start, correctionFactor);
  652. int y = extentFromRadiusEnd(after, correctionFactor);
  653. return new Corner(x, y, CornerAngles.AFTER_START, 0, -y, -x, -y);
  654. }
  655. public static Corner createStartBeforeCorner(BorderSegment start, BorderSegment before,
  656. double correctionFactor) {
  657. int width = start.getRadiusEnd();
  658. int height = before.getRadiusStart();
  659. if (width == 0 || height == 0) {
  660. return SQUARE;
  661. }
  662. int x = extentFromRadiusEnd(start, correctionFactor);
  663. int y = extentFromRadiusStart(before, correctionFactor);
  664. return new Corner(x, y, CornerAngles.START_BEFORE, x, 0, x, -y);
  665. }
  666. }
  667. /**
  668. * This is a helper class for constructing curves composed of move, line and arc operations. Coordinates
  669. * are relative to the terminal point of the previous operation
  670. */
  671. private final class PathPainter {
  672. /** Current x position */
  673. private int x;
  674. /** Current y position */
  675. private int y;
  676. PathPainter(int x, int y) throws IOException {
  677. moveTo(x, y);
  678. }
  679. private void moveTo(int x, int y) throws IOException {
  680. this.x += x;
  681. this.y += y;
  682. BorderPainter.this.moveTo(this.x, this.y);
  683. }
  684. public PathPainter lineTo(int x, int y) throws IOException {
  685. this.x += x;
  686. this.y += y;
  687. BorderPainter.this.lineTo(this.x, this.y);
  688. return this;
  689. }
  690. public PathPainter lineHorizTo(int x) throws IOException {
  691. return lineTo(x, 0);
  692. }
  693. public PathPainter lineVertTo(int y) throws IOException {
  694. return lineTo(0, y);
  695. }
  696. PathPainter drawCorner(Corner corner) throws IOException {
  697. if (corner.radiusX == 0 && corner.radiusY == 0) {
  698. return this;
  699. }
  700. if (corner.radiusX == 0 || corner.radiusY == 0) {
  701. x += corner.incrementX;
  702. y += corner.incrementY;
  703. BorderPainter.this.lineTo(x, y);
  704. return this;
  705. }
  706. BorderPainter.this.arcTo(corner.angles.start, corner.angles.end, x + corner.centerX,
  707. y + corner.centerY, corner.radiusX, corner.radiusY);
  708. x += corner.incrementX;
  709. y += corner.incrementY;
  710. return this;
  711. }
  712. }
  713. /**
  714. * Calculate the correction factor to handle over-sized elliptic corner radii.
  715. *
  716. * @param width the border width
  717. * @param height the border height
  718. * @param before the before border properties
  719. * @param after the after border properties
  720. * @param start the start border properties
  721. * @param end the end border properties
  722. *
  723. */
  724. protected static double calculateCornerCorrectionFactor(int width, int height, BorderProps before,
  725. BorderProps after, BorderProps start, BorderProps end) {
  726. return calculateCornerScaleCorrection(width, height, borderSegmentForBefore(before),
  727. borderSegmentForAfter(after), borderSegmentForStart(start), borderSegmentForEnd(end));
  728. }
  729. /**
  730. * Calculate the scaling factor to handle over-sized elliptic corner radii.
  731. *
  732. * @param width the border width
  733. * @param height the border height
  734. * @param before the before border segment
  735. * @param after the after border segment
  736. * @param start the start border segment
  737. * @param end the end border segment
  738. */
  739. protected static double calculateCornerScaleCorrection(int width, int height, BorderSegment before,
  740. BorderSegment after, BorderSegment start, BorderSegment end) {
  741. return CornerScaleCorrectionCalculator.calculate(width, height, before, after, start, end);
  742. }
  743. private static final class CornerScaleCorrectionCalculator {
  744. private double correctionFactor = 1;
  745. private CornerScaleCorrectionCalculator(int width, int height,
  746. BorderSegment before, BorderSegment after,
  747. BorderSegment start, BorderSegment end) {
  748. calculateForSegment(width, start, before, end);
  749. calculateForSegment(height, before, end, after);
  750. calculateForSegment(width, end, after, start);
  751. calculateForSegment(height, after, start, before);
  752. }
  753. public static double calculate(int width, int height,
  754. BorderSegment before, BorderSegment after,
  755. BorderSegment start, BorderSegment end) {
  756. return new CornerScaleCorrectionCalculator(width, height, before, after, start, end)
  757. .correctionFactor;
  758. }
  759. private void calculateForSegment(int width, BorderSegment bpsStart, BorderSegment bpsBefore,
  760. BorderSegment bpsEnd) {
  761. if (bpsBefore.isSpecified()) {
  762. double ellipseExtent = bpsStart.getRadiusEnd() + bpsEnd.getRadiusStart();
  763. if (ellipseExtent > 0) {
  764. double thisCorrectionFactor = width / ellipseExtent;
  765. if (thisCorrectionFactor < correctionFactor) {
  766. correctionFactor = thisCorrectionFactor;
  767. }
  768. }
  769. }
  770. }
  771. }
  772. private void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore,
  773. int style, Color color) throws IOException {
  774. graphicsPainter.drawBorderLine(x1, y1, x2, y2, horz, startOrBefore, style, color);
  775. }
  776. private void moveTo(int x, int y) throws IOException {
  777. graphicsPainter.moveTo(x, y);
  778. }
  779. private void lineTo(int x, int y) throws IOException {
  780. graphicsPainter.lineTo(x, y);
  781. }
  782. private void arcTo(final double startAngle, final double endAngle, final int cx, final int cy,
  783. final int width, final int height) throws IOException {
  784. graphicsPainter.arcTo(startAngle, endAngle, cx, cy, width, height);
  785. }
  786. private void rotateCoordinates(double angle) throws IOException {
  787. graphicsPainter.rotateCoordinates(angle);
  788. }
  789. private void translateCoordinates(int xTranslate, int yTranslate) throws IOException {
  790. graphicsPainter.translateCoordinates(xTranslate, yTranslate);
  791. }
  792. private void closePath() throws IOException {
  793. graphicsPainter.closePath();
  794. }
  795. private void clip() throws IOException {
  796. graphicsPainter.clip();
  797. }
  798. private void saveGraphicsState() throws IOException {
  799. graphicsPainter.saveGraphicsState();
  800. }
  801. private void restoreGraphicsState() throws IOException {
  802. graphicsPainter.restoreGraphicsState();
  803. }
  804. }