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.

AbstractPaintingState.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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.util;
  19. import java.awt.Color;
  20. import java.awt.geom.AffineTransform;
  21. import java.io.Serializable;
  22. import java.util.Arrays;
  23. import java.util.Collection;
  24. import java.util.List;
  25. import java.util.Stack;
  26. /**
  27. * A base class which holds information about the current painting state.
  28. */
  29. public abstract class AbstractPaintingState implements Cloneable, Serializable {
  30. private static final long serialVersionUID = 5998356138437094188L;
  31. /** current state data */
  32. private AbstractData data;
  33. /** the state stack */
  34. private StateStack<AbstractData> stateStack = new StateStack<AbstractData>();
  35. /**
  36. * Instantiates a new state data object
  37. *
  38. * @return a new state data object
  39. */
  40. protected abstract AbstractData instantiateData();
  41. /**
  42. * Instantiates a new state object
  43. *
  44. * @return a new state object
  45. */
  46. protected abstract AbstractPaintingState instantiate();
  47. /**
  48. * Returns the currently valid state
  49. *
  50. * @return the currently valid state
  51. */
  52. public AbstractData getData() {
  53. if (data == null) {
  54. data = instantiateData();
  55. }
  56. return data;
  57. }
  58. /**
  59. * Set the current color.
  60. * Check if the new color is a change and then set the current color.
  61. *
  62. * @param col the color to set
  63. * @return true if the color has changed
  64. */
  65. public boolean setColor(Color col) {
  66. Color other = getData().color;
  67. if (!org.apache.xmlgraphics.java2d.color.ColorUtil.isSameColor(col, other)) {
  68. getData().color = col;
  69. return true;
  70. }
  71. return false;
  72. }
  73. /**
  74. * Get the color.
  75. *
  76. * @return the color
  77. */
  78. public Color getColor() {
  79. if (getData().color == null) {
  80. getData().color = Color.black;
  81. }
  82. return getData().color;
  83. }
  84. /**
  85. * Get the background color.
  86. *
  87. * @return the background color
  88. */
  89. public Color getBackColor() {
  90. if (getData().backColor == null) {
  91. getData().backColor = Color.white;
  92. }
  93. return getData().backColor;
  94. }
  95. /**
  96. * Set the current background color.
  97. * Check if the new background color is a change and then set the current background color.
  98. *
  99. * @param col the background color to set
  100. * @return true if the color has changed
  101. */
  102. public boolean setBackColor(Color col) {
  103. Color other = getData().backColor;
  104. if (!org.apache.xmlgraphics.java2d.color.ColorUtil.isSameColor(col, other)) {
  105. getData().backColor = col;
  106. return true;
  107. }
  108. return false;
  109. }
  110. /**
  111. * Set the current font name
  112. *
  113. * @param internalFontName the internal font name
  114. * @return true if the font name has changed
  115. */
  116. public boolean setFontName(String internalFontName) {
  117. if (!internalFontName.equals(getData().fontName)) {
  118. getData().fontName = internalFontName;
  119. return true;
  120. }
  121. return false;
  122. }
  123. /**
  124. * Gets the current font name
  125. *
  126. * @return the current font name
  127. */
  128. public String getFontName() {
  129. return getData().fontName;
  130. }
  131. /**
  132. * Gets the current font size
  133. *
  134. * @return the current font size
  135. */
  136. public int getFontSize() {
  137. return getData().fontSize;
  138. }
  139. /**
  140. * Set the current font size.
  141. * Check if the font size is a change and then set the current font size.
  142. *
  143. * @param size the font size to set
  144. * @return true if the font size has changed
  145. */
  146. public boolean setFontSize(int size) {
  147. if (size != getData().fontSize) {
  148. getData().fontSize = size;
  149. return true;
  150. }
  151. return false;
  152. }
  153. /**
  154. * Set the current line width.
  155. *
  156. * @param width the line width in points
  157. * @return true if the line width has changed
  158. */
  159. public boolean setLineWidth(float width) {
  160. if (getData().lineWidth != width) {
  161. getData().lineWidth = width;
  162. return true;
  163. }
  164. return false;
  165. }
  166. /**
  167. * Returns the current line width
  168. *
  169. * @return the current line width
  170. */
  171. public float getLineWidth() {
  172. return getData().lineWidth;
  173. }
  174. /**
  175. * Sets the dash array (line type) for the current basic stroke
  176. *
  177. * @param dash the line dash array
  178. * @return true if the dash array has changed
  179. */
  180. public boolean setDashArray(float[] dash) {
  181. if (!Arrays.equals(dash, getData().dashArray)) {
  182. getData().dashArray = dash;
  183. return true;
  184. }
  185. return false;
  186. }
  187. /**
  188. * Get the current transform.
  189. * This gets the combination of all transforms in the
  190. * current state.
  191. *
  192. * @return the calculate combined transform for the current state
  193. */
  194. public AffineTransform getTransform() {
  195. AffineTransform at = new AffineTransform();
  196. for (AbstractData data : stateStack) {
  197. AffineTransform stackTrans = data.getTransform();
  198. at.concatenate(stackTrans);
  199. }
  200. AffineTransform currentTrans = getData().getTransform();
  201. at.concatenate(currentTrans);
  202. return at;
  203. }
  204. /**
  205. * Check the current transform.
  206. * The transform for the current state is the combination of all
  207. * transforms in the current state. The parameter is compared
  208. * against this current transform.
  209. *
  210. * @param tf the transform the check against
  211. * @return true if the new transform is different then the current transform
  212. */
  213. public boolean checkTransform(AffineTransform tf) {
  214. return !tf.equals(getData().getTransform());
  215. }
  216. /**
  217. * Get a copy of the base transform for the page. Used to translate
  218. * IPP/BPP values into X,Y positions when positioning is "fixed".
  219. *
  220. * @return the base transform, or null if the state stack is empty
  221. */
  222. public AffineTransform getBaseTransform() {
  223. if (stateStack.isEmpty()) {
  224. return null;
  225. } else {
  226. AbstractData baseData = stateStack.get(0);
  227. return (AffineTransform) baseData.getTransform().clone();
  228. }
  229. }
  230. /**
  231. * Concatenates the given AffineTransform to the current one.
  232. *
  233. * @param at the transform to concatenate to the current level transform
  234. */
  235. public void concatenate(AffineTransform at) {
  236. getData().concatenate(at);
  237. }
  238. /**
  239. * Resets the current AffineTransform to the Base AffineTransform.
  240. */
  241. public void resetTransform() {
  242. getData().setTransform(getBaseTransform());
  243. }
  244. /**
  245. * Clears the current AffineTransform to the Identity AffineTransform
  246. */
  247. public void clearTransform() {
  248. getData().clearTransform();
  249. }
  250. /**
  251. * Save the current painting state.
  252. * This pushes the current painting state onto the stack.
  253. * This call should be used when the Q operator is used
  254. * so that the state is known when popped.
  255. */
  256. public void save() {
  257. AbstractData copy = (AbstractData)getData().clone();
  258. stateStack.push(copy);
  259. }
  260. /**
  261. * Restore the current painting state.
  262. * This pops the painting state from the stack and sets current values to popped state.
  263. *
  264. * @return the restored state, null if the stack is empty
  265. */
  266. public AbstractData restore() {
  267. if (!stateStack.isEmpty()) {
  268. setData(stateStack.pop());
  269. return this.data;
  270. } else {
  271. return null;
  272. }
  273. }
  274. /**
  275. * Save all painting state data.
  276. * This pushes all painting state data in the given list to the stack
  277. *
  278. * @param dataList a state data list
  279. */
  280. public void saveAll(List<AbstractData> dataList) {
  281. for (AbstractData data : dataList) {
  282. // save current data on stack
  283. save();
  284. setData(data);
  285. }
  286. }
  287. /**
  288. * Restore all painting state data.
  289. * This pops all painting state data from the stack
  290. *
  291. * @return a list of state data popped from the stack
  292. */
  293. public List<AbstractData> restoreAll() {
  294. List<AbstractData> dataList = new java.util.ArrayList<AbstractData>();
  295. AbstractData data;
  296. while (true) {
  297. data = getData();
  298. if (restore() == null) {
  299. break;
  300. }
  301. // insert because of stack-popping
  302. dataList.add(0, data);
  303. }
  304. return dataList;
  305. }
  306. /**
  307. * Sets the current state data
  308. *
  309. * @param data the state data
  310. */
  311. protected void setData(AbstractData data) {
  312. this.data = data;
  313. }
  314. /**
  315. * Clears the state stack
  316. */
  317. public void clear() {
  318. stateStack.clear();
  319. setData(null);
  320. }
  321. /**
  322. * Return the state stack
  323. *
  324. * @return the state stack
  325. */
  326. protected Stack<AbstractData> getStateStack() {
  327. return this.stateStack;
  328. }
  329. /** {@inheritDoc} */
  330. @Override
  331. public Object clone() {
  332. AbstractPaintingState state = instantiate();
  333. state.stateStack = new StateStack<AbstractData>(this.stateStack);
  334. if (this.data != null) {
  335. state.data = (AbstractData)this.data.clone();
  336. }
  337. return state;
  338. }
  339. /** {@inheritDoc} */
  340. @Override
  341. public String toString() {
  342. return ", stateStack=" + stateStack
  343. + ", currentData=" + data;
  344. }
  345. /**
  346. * A stack implementation which holds state objects
  347. */
  348. // @SuppressFBWarnings("SE_INNER_CLASS")
  349. public class StateStack<E> extends java.util.Stack<E> {
  350. private static final long serialVersionUID = 4897178211223823041L;
  351. /**
  352. * Default constructor
  353. */
  354. public StateStack() {
  355. }
  356. /**
  357. * Copy constructor
  358. *
  359. * @param c initial contents of stack
  360. */
  361. public StateStack(Collection c) {
  362. elementCount = c.size();
  363. // 10% for growth
  364. elementData = new Object[
  365. (int)Math.min((elementCount * 110L) / 100, Integer.MAX_VALUE)];
  366. c.toArray(elementData);
  367. }
  368. }
  369. /**
  370. * A base painting state data holding object
  371. */
  372. public abstract class AbstractData implements Cloneable, Serializable {
  373. private static final long serialVersionUID = 5208418041189828624L;
  374. /** The current color */
  375. protected Color color;
  376. /** The current background color */
  377. protected Color backColor;
  378. /** The current font name */
  379. protected String fontName;
  380. /** The current font size */
  381. protected int fontSize;
  382. /** The current line width */
  383. protected float lineWidth;
  384. /** The dash array for the current basic stroke (line type) */
  385. protected float[] dashArray;
  386. /** The current transform */
  387. protected AffineTransform transform;
  388. /** The current (optional content group) layer. */
  389. protected String layer;
  390. /**
  391. * Returns a newly create data object
  392. *
  393. * @return a new data object
  394. */
  395. protected abstract AbstractData instantiate();
  396. /**
  397. * Concatenate the given AffineTransform with the current thus creating
  398. * a new viewport. Note that all concatenation operations are logged
  399. * so they can be replayed if necessary (ex. for block-containers with
  400. * "fixed" positioning.
  401. *
  402. * @param at Transformation to perform
  403. */
  404. public void concatenate(AffineTransform at) {
  405. getTransform().concatenate(at);
  406. }
  407. /**
  408. * Get the current AffineTransform.
  409. *
  410. * @return the current transform
  411. */
  412. public AffineTransform getTransform() {
  413. if (transform == null) {
  414. transform = new AffineTransform();
  415. }
  416. return transform;
  417. }
  418. /**
  419. * Sets the current AffineTransform.
  420. * @param baseTransform the transform
  421. */
  422. public void setTransform(AffineTransform baseTransform) {
  423. this.transform = baseTransform;
  424. }
  425. /**
  426. * Resets the current AffineTransform.
  427. */
  428. public void clearTransform() {
  429. transform = new AffineTransform();
  430. }
  431. public void setLayer(String layer) {
  432. if (layer != null) {
  433. this.layer = layer;
  434. } else {
  435. throw new IllegalArgumentException();
  436. }
  437. }
  438. public String getLayer() {
  439. return this.layer;
  440. }
  441. /**
  442. * Returns the derived rotation from the current transform
  443. *
  444. * @return the derived rotation from the current transform
  445. */
  446. public int getDerivedRotation() {
  447. AffineTransform at = getTransform();
  448. double sx = at.getScaleX();
  449. double sy = at.getScaleY();
  450. double shx = at.getShearX();
  451. double shy = at.getShearY();
  452. int rotation = 0;
  453. if (sx == 0 && sy == 0 && shx > 0 && shy < 0) {
  454. rotation = 270;
  455. } else if (sx < 0 && sy < 0 && shx == 0 && shy == 0) {
  456. rotation = 180;
  457. } else if (sx == 0 && sy == 0 && shx < 0 && shy > 0) {
  458. rotation = 90;
  459. } else {
  460. rotation = 0;
  461. }
  462. return rotation;
  463. }
  464. /** {@inheritDoc} */
  465. @Override
  466. public Object clone() {
  467. AbstractData data = instantiate();
  468. data.color = this.color;
  469. data.backColor = this.backColor;
  470. data.fontName = this.fontName;
  471. data.fontSize = this.fontSize;
  472. data.lineWidth = this.lineWidth;
  473. data.dashArray = this.dashArray;
  474. if (this.transform == null) {
  475. this.transform = new AffineTransform();
  476. }
  477. data.transform = new AffineTransform(this.transform);
  478. data.layer = this.layer;
  479. return data;
  480. }
  481. /** {@inheritDoc} */
  482. @Override
  483. public String toString() {
  484. return "color=" + color
  485. + ", backColor=" + backColor
  486. + ", fontName=" + fontName
  487. + ", fontSize=" + fontSize
  488. + ", lineWidth=" + lineWidth
  489. + ", dashArray=" + Arrays.toString(dashArray)
  490. + ", transform=" + transform
  491. + ", layer=" + layer;
  492. }
  493. }
  494. }