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 
18 /* $Id: LengthRangeProperty.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
19 
20 package org.apache.fop.fo.properties;
21 
22 import org.apache.fop.datatypes.CompoundDatatype;
23 import org.apache.fop.datatypes.Length;
24 import org.apache.fop.datatypes.PercentBaseContext;
25 import org.apache.fop.fo.FObj;
26 import org.apache.fop.fo.PropertyList;
27 import org.apache.fop.fo.expr.PropertyException;
28 import org.apache.fop.traits.MinOptMax;
29 import org.apache.fop.util.CompareUtil;
30 
31 /**
32  * Superclass for properties that contain LengthRange values
33  */
34 public class LengthRangeProperty extends Property implements CompoundDatatype {
35     private Property minimum;
36     private Property optimum;
37     private Property maximum;
38     private static final int MINSET = 1;
39     private static final int OPTSET = 2;
40     private static final int MAXSET = 4;
41     private int bfSet;    // bit field
42     private boolean consistent;
43 
44     /**
45      * Converts this <code>LengthRangeProperty</code> to a <code>MinOptMax</code>.
46      *
47      * @param context Percentage evaluation context
48      * @return the requested MinOptMax instance
49      */
toMinOptMax(PercentBaseContext context)50     public MinOptMax toMinOptMax(PercentBaseContext context) {
51         int min = getMinimum(context).isAuto() ? 0
52                 : getMinimum(context).getLength().getValue(context);
53         int opt = getOptimum(context).isAuto() ? min
54                 : getOptimum(context).getLength().getValue(context);
55         int max = getMaximum(context).isAuto() ? Integer.MAX_VALUE
56                 : getMaximum(context).getLength().getValue(context);
57         return MinOptMax.getInstance(min, opt, max);
58     }
59 
60     /**
61      * Inner class for a Maker for LengthProperty objects
62      */
63     public static class Maker extends CompoundPropertyMaker {
64 
65         /**
66          * @param propId the id of the property for which a Maker should be created
67          */
Maker(int propId)68         public Maker(int propId) {
69             super(propId);
70         }
71 
72         /**
73          * Create a new empty instance of LengthRangeProperty.
74          * @return the new instance.
75          */
makeNewProperty()76         public Property makeNewProperty() {
77             return new LengthRangeProperty();
78         }
79 
isNegativeLength(Length len)80         private boolean isNegativeLength(Length len) {
81             return ((len instanceof PercentLength
82                         && ((PercentLength) len).getPercentage() < 0)
83                     || (len.isAbsolute() && len.getValue() < 0));
84         }
85 
86         /** {@inheritDoc} */
convertProperty(Property p, PropertyList propertyList, FObj fo)87         public Property convertProperty(Property p,
88                                 PropertyList propertyList, FObj fo)
89                         throws PropertyException {
90 
91             if (p instanceof LengthRangeProperty) {
92                 return p;
93             }
94 
95             if (this.propId == PR_BLOCK_PROGRESSION_DIMENSION
96                     || this.propId == PR_INLINE_PROGRESSION_DIMENSION) {
97                 Length len = p.getLength();
98                 if (len != null) {
99                     if (isNegativeLength(len)) {
100                         log.warn(FObj.decorateWithContextInfo(
101                                 "Replaced negative value (" + len + ") for " + getName()
102                                 + " with 0mpt", fo));
103                         p = FixedLength.ZERO_FIXED_LENGTH;
104                     }
105                 }
106             }
107 
108             return super.convertProperty(p, propertyList, fo);
109         }
110 
111 
112         /**
113          * {@inheritDoc}
114          */
setSubprop(Property baseProperty, int subpropertyId, Property subproperty)115         protected Property setSubprop(Property baseProperty, int subpropertyId,
116                                         Property subproperty) {
117             CompoundDatatype val = (CompoundDatatype) baseProperty.getObject();
118             if (this.propId == PR_BLOCK_PROGRESSION_DIMENSION
119                     || this.propId == PR_INLINE_PROGRESSION_DIMENSION) {
120                 Length len = subproperty.getLength();
121                 if (len != null) {
122                     if (isNegativeLength(len)) {
123                         log.warn("Replaced negative value (" + len + ") for " + getName()
124                                 + " with 0mpt");
125                         val.setComponent(subpropertyId,
126                                 FixedLength.ZERO_FIXED_LENGTH, false);
127                         return baseProperty;
128                     }
129                 }
130             }
131             val.setComponent(subpropertyId, subproperty, false);
132             return baseProperty;
133         }
134 
135     }
136 
137 
138 
139     /**
140      * {@inheritDoc}
141      */
setComponent(int cmpId, Property cmpnValue, boolean bIsDefault)142     public void setComponent(int cmpId, Property cmpnValue,
143                              boolean bIsDefault) {
144         if (cmpId == CP_MINIMUM) {
145             setMinimum(cmpnValue, bIsDefault);
146         } else if (cmpId == CP_OPTIMUM) {
147             setOptimum(cmpnValue, bIsDefault);
148         } else if (cmpId == CP_MAXIMUM) {
149             setMaximum(cmpnValue, bIsDefault);
150         }
151     }
152 
153     /**
154      * {@inheritDoc}
155      */
getComponent(int cmpId)156     public Property getComponent(int cmpId) {
157         if (cmpId == CP_MINIMUM) {
158             return getMinimum(null);
159         } else if (cmpId == CP_OPTIMUM) {
160             return getOptimum(null);
161         } else if (cmpId == CP_MAXIMUM) {
162             return getMaximum(null);
163         } else {
164             return null;    // SHOULDN'T HAPPEN
165         }
166     }
167 
168     /**
169      * Set minimum value to min.
170      * @param minimum A Length value specifying the minimum value for this
171      * LengthRange.
172      * @param bIsDefault If true, this is set as a "default" value
173      * and not a user-specified explicit value.
174      */
setMinimum(Property minimum, boolean bIsDefault)175     protected void setMinimum(Property minimum, boolean bIsDefault) {
176         this.minimum = minimum;
177         if (!bIsDefault) {
178             bfSet |= MINSET;
179         }
180         consistent = false;
181     }
182 
183 
184     /**
185      * Set maximum value to max if it is &gt;= optimum or optimum isn't set.
186      * @param max A Length value specifying the maximum value for this
187      * @param bIsDefault If true, this is set as a "default" value
188      * and not a user-specified explicit value.
189      */
setMaximum(Property max, boolean bIsDefault)190     protected void setMaximum(Property max, boolean bIsDefault) {
191         maximum = max;
192         if (!bIsDefault) {
193             bfSet |= MAXSET;
194         }
195         consistent = false;
196     }
197 
198 
199     /**
200      * Set the optimum value.
201      * @param opt A Length value specifying the optimum value for this
202      * @param bIsDefault If true, this is set as a "default" value
203      * and not a user-specified explicit value.
204      */
setOptimum(Property opt, boolean bIsDefault)205     protected void setOptimum(Property opt, boolean bIsDefault) {
206         optimum = opt;
207         if (!bIsDefault) {
208             bfSet |= OPTSET;
209         }
210         consistent = false;
211     }
212 
213     // Minimum is prioritaire, if explicit
checkConsistency(PercentBaseContext context)214     private void checkConsistency(PercentBaseContext context) {
215         if (consistent) {
216             return;
217         }
218         if (context == null) {
219             return;
220         }
221         // Make sure max >= min
222         // Must also control if have any allowed enum values!
223 
224         if (!minimum.isAuto() && !maximum.isAuto()
225                 && minimum.getLength().getValue(context) > maximum.getLength().getValue(context)) {
226             if ((bfSet & MINSET) != 0) {
227                 // if minimum is explicit, force max to min
228                 if ((bfSet & MAXSET) != 0) {
229                     // Warning: min>max, resetting max to min
230                     log.error("forcing max to min in LengthRange");
231                 }
232                 maximum = minimum;
233             } else {
234                 minimum = maximum; // minimum was default value
235             }
236         }
237         // Now make sure opt <= max and opt >= min
238         if (!optimum.isAuto() && !maximum.isAuto()
239                 && optimum.getLength().getValue(context) > maximum.getLength().getValue(context)) {
240             if ((bfSet & OPTSET) != 0) {
241                 if ((bfSet & MAXSET) != 0) {
242                     // Warning: opt > max, resetting opt to max
243                     log.error("forcing opt to max in LengthRange");
244                     optimum = maximum;
245                 } else {
246                     maximum = optimum; // maximum was default value
247                 }
248             } else {
249                 // opt is default and max is explicit or default
250                 optimum = maximum;
251             }
252         } else if (!optimum.isAuto() && !minimum.isAuto()
253                     && optimum.getLength().getValue(context)
254                         < minimum.getLength().getValue(context)) {
255             if ((bfSet & MINSET) != 0) {
256                 // if minimum is explicit, force opt to min
257                 if ((bfSet & OPTSET) != 0) {
258                     log.error("forcing opt to min in LengthRange");
259                 }
260                 optimum = minimum;
261             } else {
262                 minimum = optimum; // minimum was default value
263             }
264         }
265 
266         consistent = true;
267     }
268 
269     /**
270      * @param context Percentage evaluation context
271      * @return minimum length
272      */
getMinimum(PercentBaseContext context)273     public Property getMinimum(PercentBaseContext context) {
274         checkConsistency(context);
275         return this.minimum;
276     }
277 
278     /**
279      * @param context Percentage evaluation context
280      * @return maximum length
281      */
getMaximum(PercentBaseContext context)282     public Property getMaximum(PercentBaseContext context) {
283         checkConsistency(context);
284         return this.maximum;
285     }
286 
287     /**
288      * @param context Percentage evaluation context
289      * @return optimum length
290      */
getOptimum(PercentBaseContext context)291     public Property getOptimum(PercentBaseContext context) {
292         checkConsistency(context);
293         return this.optimum;
294     }
295 
296     /** {@inheritDoc} */
toString()297     public String toString() {
298         return "LengthRange["
299             + "min:" + getMinimum(null).getObject()
300             + ", max:" + getMaximum(null).getObject()
301             + ", opt:" + getOptimum(null).getObject() + "]";
302     }
303 
304     /**
305      * @return this.lengthRange
306      */
getLengthRange()307     public LengthRangeProperty getLengthRange() {
308         return this;
309     }
310 
311     /**
312      * @return this.lengthRange cast as an Object
313      */
getObject()314     public Object getObject() {
315         return this;
316     }
317 
318     @Override
hashCode()319     public int hashCode() {
320         final int prime = 31;
321         int result = 1;
322         result = prime * result + bfSet;
323         result = prime * result + (consistent ? 1231 : 1237);
324         result = prime * result + CompareUtil.getHashCode(minimum);
325         result = prime * result + CompareUtil.getHashCode(optimum);
326         result = prime * result + CompareUtil.getHashCode(maximum);
327         return result;
328     }
329 
330     @Override
equals(Object obj)331     public boolean equals(Object obj) {
332         if (this == obj) {
333             return true;
334         }
335         if (!(obj instanceof LengthRangeProperty)) {
336             return false;
337         }
338         LengthRangeProperty other = (LengthRangeProperty) obj;
339         return bfSet == other.bfSet
340                 && consistent == other.consistent
341                 && CompareUtil.equal(minimum, other.minimum)
342                 && CompareUtil.equal(optimum, other.optimum)
343                 && CompareUtil.equal(maximum, other.maximum);
344     }
345 }
346