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.

Trait.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  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.area;
  19. import java.awt.Color;
  20. import java.io.Serializable;
  21. import org.apache.xmlgraphics.image.loader.ImageInfo;
  22. import org.apache.fop.fonts.FontTriplet;
  23. import org.apache.fop.traits.BorderProps;
  24. import org.apache.fop.traits.Direction;
  25. import org.apache.fop.traits.Visibility;
  26. import org.apache.fop.traits.WritingMode;
  27. import org.apache.fop.util.ColorUtil;
  28. import static org.apache.fop.fo.Constants.EN_NOREPEAT;
  29. import static org.apache.fop.fo.Constants.EN_REPEAT;
  30. import static org.apache.fop.fo.Constants.EN_REPEATX;
  31. import static org.apache.fop.fo.Constants.EN_REPEATY;
  32. // properties should be serialized by the holder
  33. /**
  34. * Area traits used for rendering.
  35. * This class represents an area trait that specifies a value for rendering.
  36. */
  37. public final class Trait implements Serializable {
  38. private static final long serialVersionUID = 3234280285391611437L;
  39. private Trait() {
  40. }
  41. /** Id reference line, not resolved. (not sure if this is needed.) */
  42. //public static final Integer ID_LINK = Integer.valueOf(0);
  43. /**
  44. * Internal link trait.
  45. * Contains the PageViewport key and the PROD_ID of the target area
  46. */
  47. public static final Integer INTERNAL_LINK = 1;
  48. /** * External link. A URL link to an external resource. */
  49. public static final Integer EXTERNAL_LINK = 2;
  50. /** The font triplet for the current font. */
  51. public static final Integer FONT = 3;
  52. /** Font size for the current font. */
  53. public static final Integer FONT_SIZE = 4;
  54. /** The current color. */
  55. public static final Integer COLOR = 7;
  56. /** The ID of the FO that produced an area. */
  57. public static final Integer PROD_ID = 8;
  58. /** Background trait for an area. */
  59. public static final Integer BACKGROUND = 9;
  60. /** Underline trait used when rendering inline parent. */
  61. public static final Integer UNDERLINE = 10;
  62. /** Overline trait used when rendering inline parent. */
  63. public static final Integer OVERLINE = 11;
  64. /** Linethrough trait used when rendering inline parent. */
  65. public static final Integer LINETHROUGH = 12;
  66. /** Shadow offset. */
  67. //public static final Integer OFFSET = Integer.valueOf(13);
  68. /** The shadow for text. */
  69. //public static final Integer SHADOW = Integer.valueOf(14);
  70. /** The border start. */
  71. public static final Integer BORDER_START = 15;
  72. /** The border end. */
  73. public static final Integer BORDER_END = 16;
  74. /** The border before. */
  75. public static final Integer BORDER_BEFORE = 17;
  76. /** The border after. */
  77. public static final Integer BORDER_AFTER = 18;
  78. /** The padding start. */
  79. public static final Integer PADDING_START = 19;
  80. /** The padding end. */
  81. public static final Integer PADDING_END = 20;
  82. /** The padding before. */
  83. public static final Integer PADDING_BEFORE = 21;
  84. /** The padding after. */
  85. public static final Integer PADDING_AFTER = 22;
  86. /** The space start. */
  87. public static final Integer SPACE_START = 23;
  88. /** The space end. */
  89. public static final Integer SPACE_END = 24;
  90. /** break before */
  91. //public static final Integer BREAK_BEFORE = Integer.valueOf(25);
  92. /** break after */
  93. //public static final Integer BREAK_AFTER = Integer.valueOf(26);
  94. /** The start-indent trait. */
  95. public static final Integer START_INDENT = 27;
  96. /** The end-indent trait. */
  97. public static final Integer END_INDENT = 28;
  98. /** The space-before trait. */
  99. public static final Integer SPACE_BEFORE = 29;
  100. /** The space-after trait. */
  101. public static final Integer SPACE_AFTER = 30;
  102. /** The is-reference-area trait. */
  103. public static final Integer IS_REFERENCE_AREA = 31;
  104. /** The is-viewport-area trait. */
  105. public static final Integer IS_VIEWPORT_AREA = 32;
  106. /** Blinking trait used when rendering inline parent. */
  107. public static final Integer BLINK = 33;
  108. /** Trait for color of underline decorations when rendering inline parent. */
  109. public static final Integer UNDERLINE_COLOR = 34;
  110. /** Trait for color of overline decorations when rendering inline parent. */
  111. public static final Integer OVERLINE_COLOR = 35;
  112. /** Trait for color of linethrough decorations when rendering inline parent. */
  113. public static final Integer LINETHROUGH_COLOR = 36;
  114. /** For navigation in the document structure. */
  115. public static final Integer STRUCTURE_TREE_ELEMENT = 37;
  116. /** writing mode trait */
  117. public static final Integer WRITING_MODE = 38;
  118. /** inline progression direction trait */
  119. public static final Integer INLINE_PROGRESSION_DIRECTION = 39;
  120. /** block progression direction trait */
  121. public static final Integer BLOCK_PROGRESSION_DIRECTION = 40;
  122. /** column progression direction trait */
  123. public static final Integer COLUMN_PROGRESSION_DIRECTION = 41;
  124. /** shift direction trait */
  125. public static final Integer SHIFT_DIRECTION = 42;
  126. /** For optional content groups. */
  127. public static final Integer LAYER = 43;
  128. /** Used to disable the rendering of a Block http://www.w3.org/TR/xsl/#rend-vis */
  129. public static final Integer VISIBILITY = 44;
  130. /** Maximum value used by trait keys */
  131. public static final int MAX_TRAIT_KEY = 44;
  132. private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1];
  133. private static class TraitInfo {
  134. private String name;
  135. private Class clazz; // Class of trait data
  136. public TraitInfo(String name, Class clazz) {
  137. this.name = name;
  138. this.clazz = clazz;
  139. }
  140. public String getName() {
  141. return this.name;
  142. }
  143. public Class getClazz() {
  144. return this.clazz;
  145. }
  146. }
  147. private static void put(Integer key, TraitInfo info) {
  148. TRAIT_INFO[key] = info;
  149. }
  150. static {
  151. // Create a hashmap mapping trait code to name for external representation
  152. //put(ID_LINK, new TraitInfo("id-link", String.class));
  153. put(STRUCTURE_TREE_ELEMENT, new TraitInfo("structure-tree-element", String.class));
  154. put(INTERNAL_LINK, new TraitInfo("internal-link", InternalLink.class));
  155. put(EXTERNAL_LINK, new TraitInfo("external-link", ExternalLink.class));
  156. put(FONT, new TraitInfo("font", FontTriplet.class));
  157. put(FONT_SIZE, new TraitInfo("font-size", Integer.class));
  158. put(COLOR, new TraitInfo("color", Color.class));
  159. put(PROD_ID, new TraitInfo("prod-id", String.class));
  160. put(BACKGROUND, new TraitInfo("background", Background.class));
  161. put(UNDERLINE, new TraitInfo("underline-score", Boolean.class));
  162. put(UNDERLINE_COLOR, new TraitInfo("underline-score-color", Color.class));
  163. put(OVERLINE, new TraitInfo("overline-score", Boolean.class));
  164. put(OVERLINE_COLOR, new TraitInfo("overline-score-color", Color.class));
  165. put(LINETHROUGH, new TraitInfo("through-score", Boolean.class));
  166. put(LINETHROUGH_COLOR, new TraitInfo("through-score-color", Color.class));
  167. put(BLINK, new TraitInfo("blink", Boolean.class));
  168. //put(OFFSET, new TraitInfo("offset", Integer.class));
  169. //put(SHADOW, new TraitInfo("shadow", Integer.class));
  170. put(BORDER_START, new TraitInfo("border-start", BorderProps.class));
  171. put(BORDER_END, new TraitInfo("border-end", BorderProps.class));
  172. put(BORDER_BEFORE, new TraitInfo("border-before", BorderProps.class));
  173. put(BORDER_AFTER, new TraitInfo("border-after", BorderProps.class));
  174. put(PADDING_START, new TraitInfo("padding-start", Integer.class));
  175. put(PADDING_END, new TraitInfo("padding-end", Integer.class));
  176. put(PADDING_BEFORE, new TraitInfo("padding-before", Integer.class));
  177. put(PADDING_AFTER, new TraitInfo("padding-after", Integer.class));
  178. put(SPACE_START, new TraitInfo("space-start", Integer.class));
  179. put(SPACE_END, new TraitInfo("space-end", Integer.class));
  180. //put(BREAK_BEFORE, new TraitInfo("break-before", Integer.class));
  181. //put(BREAK_AFTER, new TraitInfo("break-after", Integer.class));
  182. put(START_INDENT, new TraitInfo("start-indent", Integer.class));
  183. put(END_INDENT, new TraitInfo("end-indent", Integer.class));
  184. put(SPACE_BEFORE, new TraitInfo("space-before", Integer.class));
  185. put(SPACE_AFTER, new TraitInfo("space-after", Integer.class));
  186. put(IS_REFERENCE_AREA, new TraitInfo("is-reference-area", Boolean.class));
  187. put(IS_VIEWPORT_AREA, new TraitInfo("is-viewport-area", Boolean.class));
  188. put(WRITING_MODE,
  189. new TraitInfo("writing-mode", WritingMode.class));
  190. put(INLINE_PROGRESSION_DIRECTION,
  191. new TraitInfo("inline-progression-direction", Direction.class));
  192. put(BLOCK_PROGRESSION_DIRECTION,
  193. new TraitInfo("block-progression-direction", Direction.class));
  194. put(SHIFT_DIRECTION,
  195. new TraitInfo("shift-direction", Direction.class));
  196. put(LAYER, new TraitInfo("layer", String.class));
  197. put(VISIBILITY, new TraitInfo("visibility", Visibility.class));
  198. }
  199. /**
  200. * Get the trait name for a trait code.
  201. *
  202. * @param traitCode the trait code to get the name for
  203. * @return the trait name
  204. */
  205. public static String getTraitName(Object traitCode) {
  206. return TRAIT_INFO[(Integer)traitCode].getName();
  207. }
  208. /**
  209. * Get the data storage class for the trait.
  210. *
  211. * @param traitCode the trait code to lookup
  212. * @return the class type for the trait
  213. */
  214. public static Class getTraitClass(Object traitCode) {
  215. return TRAIT_INFO[(Integer)traitCode].getClazz();
  216. }
  217. /**
  218. * Class for internal link traits.
  219. * Stores PageViewport key and producer ID
  220. */
  221. public static class InternalLink implements Serializable {
  222. private static final long serialVersionUID = -8993505060996723039L;
  223. /** The unique key of the PageViewport. */
  224. private String pvKey;
  225. /** The PROD_ID of the link target */
  226. private String idRef;
  227. /**
  228. * Create an InternalLink to the given PageViewport and target ID
  229. *
  230. * @param pvKey the PageViewport key
  231. * @param idRef the target ID
  232. */
  233. public InternalLink(String pvKey, String idRef) {
  234. setPVKey(pvKey);
  235. setIDRef(idRef);
  236. }
  237. /**
  238. * Create an InternalLink based on the given XML attribute value.
  239. * This is typically called when data are read from an XML area tree.
  240. *
  241. * @param attrValue attribute value to be parsed by InternalLink.parseXMLAttribute
  242. */
  243. public InternalLink(String attrValue) {
  244. String[] values = parseXMLAttribute(attrValue);
  245. setPVKey(values[0]);
  246. setIDRef(values[1]);
  247. }
  248. /**
  249. * Sets the key of the targeted PageViewport.
  250. *
  251. * @param pvKey the PageViewport key
  252. */
  253. public void setPVKey(String pvKey) {
  254. this.pvKey = pvKey;
  255. }
  256. /**
  257. * Returns the key of the targeted PageViewport.
  258. *
  259. * @return the PageViewport key
  260. */
  261. public String getPVKey() {
  262. return pvKey;
  263. }
  264. /**
  265. * Sets the target ID.
  266. *
  267. * @param idRef the target ID
  268. */
  269. public void setIDRef(String idRef) {
  270. this.idRef = idRef;
  271. }
  272. /**
  273. * Returns the target ID.
  274. *
  275. * @return the target ID
  276. */
  277. public String getIDRef() {
  278. return idRef;
  279. }
  280. /**
  281. * Returns the attribute value for this object as
  282. * used in the area tree XML.
  283. *
  284. * @return a string of the type "(thisPVKey,thisIDRef)"
  285. */
  286. public String xmlAttribute() {
  287. return makeXMLAttribute(pvKey, idRef);
  288. }
  289. /**
  290. * Returns the XML attribute value for the given PV key and ID ref.
  291. * This value is used in the area tree XML.
  292. *
  293. * @param pvKey the PageViewport key of the link target
  294. * @param idRef the ID of the link target
  295. * @return a string of the type "(thisPVKey,thisIDRef)"
  296. */
  297. public static String makeXMLAttribute(String pvKey, String idRef) {
  298. return "(" + (pvKey == null ? "" : pvKey) + ","
  299. + (idRef == null ? "" : idRef) + ")";
  300. }
  301. /**
  302. * Parses XML attribute value from the area tree into
  303. * PageViewport key + IDRef strings. If the attribute value is
  304. * formatted like "(s1,s2)", then s1 and s2 are considered to be
  305. * the PV key and the IDRef, respectively.
  306. * Otherwise, the entire string is the PV key and the IDRef is null.
  307. *
  308. * @param attrValue the atribute value (PV key and possibly IDRef)
  309. * @return a 2-String array containing the PV key and the IDRef.
  310. * Both may be null.
  311. */
  312. public static String[] parseXMLAttribute(String attrValue) {
  313. String[] result = {null, null};
  314. if (attrValue != null) {
  315. int len = attrValue.length();
  316. if (len >= 2 && attrValue.charAt(0) == '(' && attrValue.charAt(len - 1) == ')'
  317. && attrValue.indexOf(',') != -1) {
  318. String value = attrValue.substring(1, len - 1); // remove brackets
  319. int delimIndex = value.indexOf(',');
  320. result[0] = value.substring(0, delimIndex).trim(); // PV key
  321. result[1] = value.substring(delimIndex + 1, value.length()).trim(); // IDRef
  322. } else {
  323. // PV key only, e.g. from old area tree XML:
  324. result[0] = attrValue;
  325. }
  326. }
  327. return result;
  328. }
  329. /**
  330. * Return the human-friendly string for debugging.
  331. * {@inheritDoc}
  332. */
  333. @Override
  334. public String toString() {
  335. StringBuffer sb = new StringBuffer();
  336. sb.append("pvKey=").append(pvKey);
  337. sb.append(",idRef=").append(idRef);
  338. return sb.toString();
  339. }
  340. }
  341. /**
  342. * External Link trait structure
  343. */
  344. public static class ExternalLink implements Serializable {
  345. private static final long serialVersionUID = -3720707599232620946L;
  346. private String destination;
  347. private boolean newWindow;
  348. /**
  349. * Constructs an ExternalLink object with the given destination
  350. *
  351. * @param destination target of the link
  352. * @param newWindow true if the target should be opened in a new window
  353. */
  354. public ExternalLink(String destination, boolean newWindow) {
  355. this.destination = destination;
  356. this.newWindow = newWindow;
  357. }
  358. /**
  359. * Create an <code>ExternalLink</code> from a trait value/attribute value in the
  360. * area tree
  361. * @param traitValue the value to use (should match the result of {@link #toString()}
  362. * @return an <code>ExternalLink</code> instance corresponding to the given value
  363. */
  364. protected static ExternalLink makeFromTraitValue(String traitValue) {
  365. String dest = null;
  366. boolean newWindow = false;
  367. String[] values = traitValue.split(",");
  368. for (String v : values) {
  369. if (v.startsWith("dest=")) {
  370. dest = v.substring(5);
  371. } else if (v.startsWith("newWindow=")) {
  372. newWindow = Boolean.valueOf(v.substring(10));
  373. } else {
  374. throw new IllegalArgumentException(
  375. "Malformed trait value for Trait.ExternalLink: " + traitValue);
  376. }
  377. }
  378. return new ExternalLink(dest, newWindow);
  379. }
  380. /**
  381. * Get the target/destination of the link
  382. * @return the destination of the link
  383. */
  384. public String getDestination() {
  385. return this.destination;
  386. }
  387. /**
  388. * Check if the target has to be displayed in a new window
  389. * @return <code>true</code> if the target has to be displayed in a new window
  390. */
  391. public boolean newWindow() {
  392. return this.newWindow;
  393. }
  394. /**
  395. * Return a String representation of the object.
  396. * @return a <code>String</code> of the form
  397. * "org.apache.fop.area.Trait.ExternalLink[dest=someURL,newWindow=false]"
  398. */
  399. @Override
  400. public String toString() {
  401. StringBuffer sb = new StringBuffer(64);
  402. sb.append("newWindow=").append(newWindow);
  403. sb.append(",dest=").append(this.destination);
  404. return sb.toString();
  405. }
  406. }
  407. /**
  408. * Background trait structure.
  409. * Used for storing back trait information which are related.
  410. */
  411. public static class Background implements Serializable {
  412. private static final long serialVersionUID = 8452078676273242870L;
  413. /** The background color if any. */
  414. private Color color;
  415. /** The background image url if any. */
  416. private String url;
  417. /** The background image if any. */
  418. private ImageInfo imageInfo;
  419. /** Background repeat enum for images. */
  420. private int repeat;
  421. /** Background horizontal offset for images. */
  422. private int horiz;
  423. /** Background vertical offset for images. */
  424. private int vertical;
  425. private int imageTargetWidth;
  426. private int imageTargetHeight;
  427. /**
  428. * Returns the background color.
  429. * @return background color, null if n/a
  430. */
  431. public Color getColor() {
  432. return color;
  433. }
  434. /**
  435. * Returns the horizontal offset for images.
  436. * @return the horizontal offset
  437. */
  438. public int getHoriz() {
  439. return horiz;
  440. }
  441. /**
  442. * Returns the image repetition behaviour for images.
  443. * @return the image repetition behaviour
  444. */
  445. public int getRepeat() {
  446. return repeat;
  447. }
  448. /**
  449. * Returns the URL to the background image
  450. * @return URL to the background image, null if n/a
  451. */
  452. public String getURL() {
  453. return url;
  454. }
  455. /**
  456. * Returns the ImageInfo object representing the background image
  457. * @return the background image, null if n/a
  458. */
  459. public ImageInfo getImageInfo() {
  460. return imageInfo;
  461. }
  462. /**
  463. * Returns the vertical offset for images.
  464. * @return the vertical offset
  465. */
  466. public int getVertical() {
  467. return vertical;
  468. }
  469. /**
  470. * Sets the color.
  471. * @param color The color to set
  472. */
  473. public void setColor(Color color) {
  474. this.color = color;
  475. }
  476. /**
  477. * Sets the horizontal offset.
  478. * @param horiz The horizontal offset to set
  479. */
  480. public void setHoriz(int horiz) {
  481. this.horiz = horiz;
  482. }
  483. /**
  484. * Sets the image repetition behaviour for images.
  485. * @param repeat The image repetition behaviour to set
  486. */
  487. public void setRepeat(int repeat) {
  488. this.repeat = repeat;
  489. }
  490. /**
  491. * Sets the image repetition behaviour for images.
  492. * @param repeat The image repetition behaviour to set
  493. */
  494. public void setRepeat(String repeat) {
  495. setRepeat(getConstantForRepeat(repeat));
  496. }
  497. /**
  498. * Sets the URL to the background image.
  499. * @param url The URL to set
  500. */
  501. public void setURL(String url) {
  502. this.url = url;
  503. }
  504. /**
  505. * Sets the ImageInfo of the image to use as the background image.
  506. * @param info The background image's info object
  507. */
  508. public void setImageInfo(ImageInfo info) {
  509. this.imageInfo = info;
  510. }
  511. /**
  512. * Sets the vertical offset for images.
  513. * @param vertical The vertical offset to set
  514. */
  515. public void setVertical(int vertical) {
  516. this.vertical = vertical;
  517. }
  518. private String getRepeatString() {
  519. switch (getRepeat()) {
  520. case EN_REPEAT: return "repeat";
  521. case EN_REPEATX: return "repeat-x";
  522. case EN_REPEATY: return "repeat-y";
  523. case EN_NOREPEAT: return "no-repeat";
  524. default: throw new IllegalStateException("Illegal repeat style: " + getRepeat());
  525. }
  526. }
  527. private static int getConstantForRepeat(String repeat) {
  528. if ("repeat".equalsIgnoreCase(repeat)) {
  529. return EN_REPEAT;
  530. } else if ("repeat-x".equalsIgnoreCase(repeat)) {
  531. return EN_REPEATX;
  532. } else if ("repeat-y".equalsIgnoreCase(repeat)) {
  533. return EN_REPEATY;
  534. } else if ("no-repeat".equalsIgnoreCase(repeat)) {
  535. return EN_NOREPEAT;
  536. } else {
  537. throw new IllegalStateException("Illegal repeat style: " + repeat);
  538. }
  539. }
  540. /**
  541. * Return the string for debugging.
  542. * {@inheritDoc}
  543. */
  544. @Override
  545. public String toString() {
  546. StringBuffer sb = new StringBuffer();
  547. if (color != null) {
  548. sb.append("color=").append(ColorUtil.colorToString(color));
  549. }
  550. if (url != null) {
  551. if (color != null) {
  552. sb.append(",");
  553. }
  554. sb.append("url=").append(url);
  555. sb.append(",repeat=").append(getRepeatString());
  556. sb.append(",horiz=").append(horiz);
  557. sb.append(",vertical=").append(vertical);
  558. }
  559. if (imageTargetWidth != 0) {
  560. sb.append(",target-width=").append(Integer.toString(imageTargetWidth));
  561. }
  562. if (imageTargetHeight != 0) {
  563. sb.append(",target-height=").append(Integer.toString(imageTargetHeight));
  564. }
  565. return sb.toString();
  566. }
  567. public void setImageTargetWidth(int value) {
  568. imageTargetWidth = value;
  569. }
  570. public int getImageTargetWidth() {
  571. return imageTargetWidth;
  572. }
  573. public void setImageTargetHeight(int value) {
  574. imageTargetHeight = value;
  575. }
  576. public int getImageTargetHeight() {
  577. return imageTargetHeight;
  578. }
  579. }
  580. }