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.

BorderPainter.java 35KB

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