From f6d67fea949f28cd5738433ad5a3aae1fd94b3fa Mon Sep 17 00:00:00 2001 From: danfickle Date: Mon, 14 Jun 2021 18:43:48 +1000 Subject: [PATCH] #364 Finally got boxing correct including for block content in footnotes. Also cleanup and documentation around styling code. --- .../openhtmltopdf/context/StyleReference.java | 189 ------------------ .../css/newmatch/CascadedStyle.java | 83 +------- .../css/style/CalculatedStyle.java | 139 ++++++++----- .../style/derived/DerivedValueFactory.java | 4 + .../com/openhtmltopdf/layout/BoxBuilder.java | 187 ++++++++++------- .../openhtmltopdf/layout/SharedContext.java | 36 ++-- .../java/com/openhtmltopdf/render/Box.java | 52 ----- .../openhtmltopdf/render/InlineLayoutBox.java | 28 +-- .../com/openhtmltopdf/render/LineBox.java | 21 +- .../html/issue-364-footnotes-blocks.html | 4 +- .../VisualRegressionTest.java | 7 +- 11 files changed, 239 insertions(+), 511 deletions(-) diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/StyleReference.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/StyleReference.java index c926abc8b..9d54b1b86 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/StyleReference.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/StyleReference.java @@ -268,192 +268,3 @@ public void setSupportCMYKColors(boolean b) { _stylesheetFactory.setSupportCMYKColors(b); } } - -/* - * $Id$ - * - * $Log$ - * Revision 1.22 2008/07/27 00:21:46 peterbrant - * Implement CMYK color support for PDF output, starting with patch from Mykola Gurov / Banish java.awt.Color from FS core layout classes - * - * Revision 1.21 2008/04/04 13:32:38 peterbrant - * Fix method name - * - * Revision 1.20 2008/04/04 13:28:38 peterbrant - * Make sure user agent is provided to StyleReference when it's modified / Light cleanup - * - * Revision 1.19 2008/01/22 00:29:23 peterbrant - * Need to propagate changes to user agent in SharedContext to containing StyleReference - * - * Revision 1.18 2007/10/31 23:14:42 peterbrant - * Add rudimentary support for @font-face rules - * - * Revision 1.17 2007/08/19 22:22:52 peterbrant - * Merge R8pbrant changes to HEAD - * - * Revision 1.16.2.1 2007/07/09 22:18:04 peterbrant - * Begin work on running headers and footers and named pages - * - * Revision 1.16 2007/05/26 19:04:13 peterbrant - * Implement support for removing all references to a particular Element (in order to support limited dynamic DOM changes) - * - * Revision 1.15 2007/05/20 23:25:34 peterbrant - * Various code cleanups (e.g. remove unused imports) - * - * Patch from Sean Bright - * - * Revision 1.14 2007/05/16 22:27:14 peterbrant - * Only load default stylesheet once - * - * Revision 1.13 2007/02/20 23:44:51 peterbrant - * Minor formatting change - * - * Revision 1.12 2007/02/20 01:17:10 peterbrant - * Start CSS parser cleanup - * - * Revision 1.11 2007/02/19 14:53:42 peterbrant - * Integrate new CSS parser - * - * Revision 1.10 2006/09/11 19:23:29 peterbrant - * Parse element styles all at once - * - * Revision 1.9 2006/08/27 00:36:14 peterbrant - * Initial commit of (initial) R7 work - * - * Revision 1.8 2006/01/03 23:02:37 peterbrant - * Remove unused variable - * - * Revision 1.7 2005/12/30 01:32:43 peterbrant - * First merge of parts of pagination work - * - * Revision 1.6 2005/11/11 01:33:15 peterbrant - * Add ability to clear all cached stylesheets - * - * Revision 1.5 2005/10/27 00:08:51 tobega - * Sorted out Context into RenderingContext and LayoutContext - * - * Revision 1.4 2005/06/26 15:48:10 tobega - * Converted to almost standard html4 default css, which shook out a bug: position should not inherit - * - * Revision 1.3 2005/06/25 19:27:46 tobega - * UAC now supplies Resources - * - * Revision 1.2 2005/06/23 17:03:40 tobega - * css now independent of DOM - * - * Revision 1.1 2005/06/22 23:48:40 tobega - * Refactored the css package to allow a clean separation from the core. - * - * Revision 1.34 2005/06/16 12:59:23 pdoubleya - * Cleaned up support for reloading documents. - * - * Revision 1.33 2005/06/16 11:29:12 pdoubleya - * First cut support for reload page, flushes inline stylesheets. - * - * Revision 1.32 2005/06/16 07:24:48 tobega - * Fixed background image bug. - * Caching images in browser. - * Enhanced LinkListener. - * Some house-cleaning, playing with Idea's code inspection utility. - * - * Revision 1.31 2005/06/15 11:53:45 tobega - * Changed UserAgentCallback to getInputStream instead of getReader. Fixed up some consequences of previous change. - * - * Revision 1.30 2005/06/01 21:36:37 tobega - * Got image scaling working, and did some refactoring along the way - * - * Revision 1.29 2005/05/17 06:56:23 tobega - * Inline backgrounds now work correctly, as does mixing of inlines and blocks for style inheritance - * - * Revision 1.28 2005/05/08 15:37:29 tobega - * Fixed up style caching so it really works (internalize CascadedStyles and let each CalculatedStyle keep track of its derived children) - * - * Revision 1.27 2005/05/08 14:51:22 tobega - * Removed the need for the Styler - * - * Revision 1.26 2005/05/08 14:36:54 tobega - * Refactored away the need for having a context in a CalculatedStyle - * - * Revision 1.25 2005/03/24 23:18:38 pdoubleya - * Added use of SharedContext (Kevin). - * - * Revision 1.24 2005/01/29 20:19:22 pdoubleya - * Clean/reformat code. Removed commented blocks, checked copyright. - * - * Revision 1.23 2005/01/29 12:49:24 pdoubleya - * Fixed cast on get...PropertiesMap(). - * - * Revision 1.22 2005/01/24 19:01:09 pdoubleya - * Mass checkin. Changed to use references to CSSName, which now has a Singleton instance for each property, everywhere property names were being used before. Removed commented code. Cascaded and Calculated style now store properties in arrays rather than maps, for optimization. - * - * Revision 1.21 2005/01/24 14:36:30 pdoubleya - * Mass commit, includes: updated for changes to property declaration instantiation, and new use of DerivedValue. Removed any references to older XR... classes (e.g. XRProperty). Cleaned imports. - * - * Revision 1.20 2005/01/16 18:50:03 tobega - * Re-introduced caching of styles, which make hamlet and alice scroll nicely again. Background painting still slow though. - * - * Revision 1.19 2005/01/08 15:56:54 tobega - * Further work on extensibility interfaces. Documented it - see website. - * - * Revision 1.18 2005/01/08 11:55:16 tobega - * Started massaging the extension interfaces - * - * Revision 1.17 2005/01/04 10:19:11 tobega - * resolve selectors to styles direcly on match, should reduce memory footprint and not affect speed very much. - * - * Revision 1.16 2005/01/03 23:40:40 tobega - * Cleaned out unnecessary styling/matching code. styling/matching is now called during boxing/rendering rather than as a separate stage. - * - * Revision 1.15 2004/12/29 10:39:27 tobega - * Separated current state Context into LayoutContext and the rest into SharedContext. - * - * Revision 1.14 2004/12/28 01:48:22 tobega - * More cleaning. Magically, the financial report demo is starting to look reasonable, without any effort being put on it. - * - * Revision 1.13 2004/12/11 18:18:08 tobega - * Still broken, won't even compile at the moment. Working hard to fix it, though. Replace the StyleReference interface with our only concrete implementation, it was a bother changing in two places all the time. - * - * Revision 1.22 2004/12/05 18:11:36 tobega - * Now uses style cache for pseudo-element styles. Also started preparing to replace inline node handling with inline content handling. - * - * Revision 1.21 2004/12/05 14:35:38 tobega - * Cleaned up some usages of Node (and removed unused stuff) in layout code. The goal is to pass "better" objects than Node wherever possible in an attempt to shake out the bugs in tree-traversal (probably often unnecessary tree-traversal) - * - * Revision 1.20 2004/12/05 00:48:53 tobega - * Cleaned up so that now all property-lookups use the CalculatedStyle. Also added support for relative values of top, left, width, etc. - * - * Revision 1.19 2004/12/02 19:46:35 tobega - * Refactored handling of inline styles to fit with StylesheetInfo and media handling (is also now correct if there should be more than one style element) - * - * Revision 1.18 2004/12/01 14:02:51 joshy - * modified media to use the value from the rendering context - * added the inline-block box - * - j - * - * Revision 1.17 2004/11/30 23:47:56 tobega - * At-media rules should now work (not tested). Also fixed at-import rules, which got broken at previous modification. - * - * Revision 1.16 2004/11/29 23:25:37 tobega - * Had to redo thinking about Stylesheets and StylesheetInfos. Now StylesheetInfos are passed around instead of Stylesheets because any Stylesheet should only be linked to its URI. Bonus: the external sheets get lazy-loaded only if needed for the medium. - * - * Revision 1.15 2004/11/28 23:29:00 tobega - * Now handles media on Stylesheets, still need to handle at-media-rules. The media-type should be set in Context.media (set by default to "screen") before calling setContext on StyleReference. - * - * Revision 1.14 2004/11/15 22:22:08 tobega - * Now handles @import stylesheets - * - * Revision 1.13 2004/11/15 19:46:13 tobega - * Refactoring in preparation for handling @import stylesheets - * - * Revision 1.12 2004/11/15 12:42:22 pdoubleya - * Across this checkin (all may not apply to this particular file) - * Changed default/package-access members to private. - * Changed to use XRRuntimeException where appropriate. - * Began move from System.err.println to std logging. - * Standard code reformat. - * Removed some unnecessary SAC member variables that were only used in initialization. - * CVS log section. - * - * - */ - diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/newmatch/CascadedStyle.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/newmatch/CascadedStyle.java index 13e951645..365672070 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/newmatch/CascadedStyle.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/newmatch/CascadedStyle.java @@ -59,8 +59,8 @@ public class CascadedStyle { /** * Map of PropertyDeclarations, keyed by {@link CSSName} */ - private Map cascadedProperties; - + private final Map cascadedProperties; + private String fingerprint; /** @@ -136,8 +136,9 @@ private CascadedStyle(CascadedStyle startingPoint, Iterator private CascadedStyle() { cascadedProperties = new TreeMap<>(); } + /** - * Creates a CascadedStyle, setting the display property to + * Creates an otherwise empty CascadedStyle, setting the display property * to the value of the display parameter. */ public static CascadedStyle createAnonymousStyle(IdentValue display) { @@ -190,8 +191,9 @@ private void addProperties(java.util.Iterator iter) { * @return True if the property is defined in this set. */ public boolean hasProperty(CSSName cssName) { - return cascadedProperties.get(cssName) != null; + return cascadedProperties.containsKey(cssName); } + /** * Returns a {@link com.openhtmltopdf.css.sheet.PropertyDeclaration} by CSS * property name, e.g. "font-family". Properties are already cascaded during @@ -243,75 +245,4 @@ public String getFingerprint() { } return this.fingerprint; } -}// end class - -/* - * $Id$ - * - * $Log$ - * Revision 1.19 2007/10/31 23:14:41 peterbrant - * Add rudimentary support for @font-face rules - * - * Revision 1.18 2007/04/12 12:29:11 peterbrant - * Properly handle floated tables with captions - * - * Revision 1.17 2007/02/20 17:23:15 peterbrant - * Optimize fingerprint calculation - * - * Revision 1.16 2007/02/20 01:17:09 peterbrant - * Start CSS parser cleanup - * - * Revision 1.15 2007/02/07 16:33:14 peterbrant - * Initial commit of rewritten table support and associated refactorings - * - * Revision 1.14 2006/06/15 20:02:39 tobega - * Using a TreeMap to get properties in sorted order should be able to reduce the size of the caches in CalculatedStyle when styles are the same apart from order of declaration of properties. - * - * Revision 1.13 2006/01/09 23:22:24 peterbrant - * Cache fingerprint after initial creation - * - * Revision 1.12 2005/10/20 20:48:02 pdoubleya - * Updates for refactoring to style classes. CalculatedStyle now has lookup methods to cover all general cases, so propertyByName() is private, which means the backing classes for styling were able to be replaced. - * - * Revision 1.11 2005/06/21 08:06:47 pdoubleya - * Changed to use Map of properties again. - * - * Revision 1.10 2005/05/16 13:48:58 tobega - * Fixe inline border mismatch and started on styling problem in switching between blocks and inlines - * - * Revision 1.9 2005/05/08 15:37:26 tobega - * Fixed up style caching so it really works (internalize CascadedStyles and let each CalculatedStyle keep track of its derived children) - * - * Revision 1.8 2005/05/08 13:02:36 tobega - * Fixed a bug whereby styles could get lost for inline elements, notably if root element was inline. Did a few other things which probably has no importance at this moment, e.g. refactored out some unused stuff. - * - * Revision 1.7 2005/04/20 14:13:07 tobega - * Issue number: - * Obtained from: - * Submitted by: - * Reviewed by: - * - * Revision 1.6 2005/01/29 20:22:25 pdoubleya - * Clean/reformat code. Removed commented blocks, checked copyright. - * - * Revision 1.5 2005/01/25 14:45:55 pdoubleya - * Added support for IdentValue mapping on property declarations. On both CascadedStyle and PropertyDeclaration you can now request the value as an IdentValue, for object-object comparisons. Updated 99% of references that used to get the string value of PD to return the IdentValue instead; remaining cases are for pseudo-elements where the PD content needs to be manipulated as a String. - * - * Revision 1.4 2005/01/24 19:01:06 pdoubleya - * Mass checkin. Changed to use references to CSSName, which now has a Singleton instance for each property, everywhere property names were being used before. Removed commented code. Cascaded and Calculated style now store properties in arrays rather than maps, for optimization. - * - * Revision 1.3 2004/11/15 13:40:14 pdoubleya - * Updated JavaDoc. - * - * Revision 1.2 2004/11/15 12:42:22 pdoubleya - * Across this checkin (all may not apply to this particular file) - * Changed default/package-access members to private. - * Changed to use XRRuntimeException where appropriate. - * Began move from System.err.println to std logging. - * Standard code reformat. - * Removed some unnecessary SAC member variables that were only used in initialization. - * CVS log section. - * - * - */ - +} diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java index 10ec71ee4..3a6ef1099 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java @@ -21,7 +21,6 @@ package com.openhtmltopdf.css.style; import java.awt.Cursor; -import java.lang.annotation.Documented; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -29,6 +28,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; +import com.openhtmltopdf.context.StyleReference; import com.openhtmltopdf.css.constants.CSSName; import com.openhtmltopdf.css.constants.IdentValue; import com.openhtmltopdf.css.newmatch.CascadedStyle; @@ -50,6 +50,9 @@ import com.openhtmltopdf.css.style.derived.NumberValue; import com.openhtmltopdf.css.style.derived.RectPropertySet; import com.openhtmltopdf.css.value.FontSpecification; +import com.openhtmltopdf.layout.BoxBuilder; +import com.openhtmltopdf.layout.LayoutContext; +import com.openhtmltopdf.layout.SharedContext; import com.openhtmltopdf.layout.counter.RootCounterContext; import com.openhtmltopdf.newtable.TableBox; import com.openhtmltopdf.render.Box; @@ -67,16 +70,23 @@ /** * A set of properties that apply to a single Element, derived from all matched * properties following the rules for CSS cascade, inheritance, importance, - * specificity and sequence. A derived style is just like a style but - * (presumably) has additional information that allows relative properties to be - * assigned values, e.g. font attributes. Property values are fully resolved - * when this style is created. A property retrieved by name should always have - * only one value in this class (e.g. one-one map). Any methods to retrieve + * specificity and sequence. A property retrieved by name should always have + * exactly one value in this class (e.g. one-one map). Some methods to retrieve * property values from an instance of this class require a valid {@link - * com.openhtmltopdf.layout.LayoutContext} be given to it, for some cases of property - * resolution. Generally, a programmer will not use this class directly, but - * will retrieve properties using a {@link com.openhtmltopdf.context.StyleReference} - * implementation. + * com.openhtmltopdf.layout.LayoutContext} be given to them. + *

+ * This is the go to class for working with a resolved style. Generally, you can get a + * instance for a box by calling: + * + *
    + *
  • {@link Box#getStyle()} after a box has been created by the {@link BoxBuilder}
  • + *
  • {@link SharedContext#getStyle(org.w3c.dom.Element)} for an element
  • + *
  • {@link StyleReference#getPseudoElementStyle(org.w3c.dom.Node, String)} for a pseudo + * element. StyleReference is available from {@link LayoutContext}
  • + *
  • {@link #deriveStyle(CascadedStyle)}
  • to create a child style (non-inherited + * property values will not be available from the child style + *
  • {@link EmptyStyle} to start with nothing
  • + *
* * @author Torbjoern Gannholm * @author Patrick Wright @@ -101,19 +111,10 @@ public class CalculatedStyle { private boolean _paddingAllowed = true; private boolean _bordersAllowed = true; - private BackgroundSize _backgroundSize; - /** * Cache child styles of this style that have the same cascaded properties */ private final java.util.Map _childCache = new java.util.HashMap<>(); - /*private java.util.HashMap _childCache = new java.util.LinkedHashMap(5, 0.75f, true) { - private static final int MAX_ENTRIES = 10; - - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > MAX_ENTRIES; - } - };*/ /** * Our main array of property values defined in this style, keyed @@ -151,6 +152,11 @@ private CalculatedStyle(CalculatedStyle parent, CascadedStyle matched) { this(); _parent = parent; + init(matched); + } + + + private void init(CascadedStyle matched) { derive(matched); checkPaddingAllowed(); @@ -186,9 +192,11 @@ private void checkBordersAllowed() { } /** - * derives a child style from this style. - *
- * depends on the ability to return the identical CascadedStyle each time a child style is needed + * Derives a child style from this style. Non-inherited properties + * such as borders will be replaced compared to this which is used + * as parent style. + *

+ * Depends on the ability to return the identical CascadedStyle each time a child style is needed * * @param matched the CascadedStyle to apply * @return The derived child style @@ -209,12 +217,37 @@ public CalculatedStyle deriveStyle(CascadedStyle matched) { return cs; } - public int countAssigned() { - int c = 0; - for (int i = 0; i < _derivedValuesById.length; i++) { - if (_derivedValuesById[i] != null) c++; - } - return c; + /** + * Override this style with specified styles. This will NOT + * create a child style, rather an exact copy with only the specified + * properties overridden. Compare to {@link #deriveStyle(CascadedStyle)}. + */ + public CalculatedStyle overrideStyle(CascadedStyle matched) { + CalculatedStyle ret = new CalculatedStyle(); + + ret._parent = this._parent; + System.arraycopy(this._derivedValuesById, 0, ret._derivedValuesById, 0, ret._derivedValuesById.length); + + init(matched); + + return ret; + } + + /** + * Override this style with specified styles. This will NOT + * create a child style, rather an exact copy with only the display + * property overridden. Compare to {@link #createAnonymousStyle(IdentValue)} + * which creates a child style. + */ + public CalculatedStyle overrideStyle(IdentValue display) { + CalculatedStyle ret = new CalculatedStyle(); + + ret._parent = this._parent; + System.arraycopy(this._derivedValuesById, 0, ret._derivedValuesById, 0, ret._derivedValuesById.length); + + init(CascadedStyle.createAnonymousStyle(display)); + + return ret; } /** @@ -257,12 +290,6 @@ public String[] asStringArray(CSSName cssName) { return valueByName(cssName).asStringArray(); } - public void setDefaultValue(CSSName cssName, FSDerivedValue fsDerivedValue) { - if (_derivedValuesById[cssName.FS_ID] == null) { - _derivedValuesById[cssName.FS_ID] = fsDerivedValue; - } - } - // TODO: doc public boolean hasAbsoluteUnit(CSSName cssName) { boolean isAbs = false; @@ -520,9 +547,11 @@ public boolean isLengthOrNumber(CSSName cssName) { /** * Returns a {@link FSDerivedValue} by name. Because we are a derived * style, the property will already be resolved at this point. + *

+ * This will look up the ancestor tree for inherited properties and + * use an initial value for unspecified properties which do not inherit. * * @param cssName The CSS property name, e.g. "font-family" - * @return See desc. */ public FSDerivedValue valueByName(CSSName cssName) { FSDerivedValue val = _derivedValuesById[cssName.FS_ID]; @@ -535,7 +564,6 @@ public FSDerivedValue valueByName(CSSName cssName) { // for the value if (! needInitialValue && CSSName.propertyInherits(cssName) && _parent != null - // && (val = _parent.valueByName(cssName)) != null) { // Do nothing, val is already set } else { @@ -554,35 +582,33 @@ public FSDerivedValue valueByName(CSSName cssName) { } _derivedValuesById[cssName.FS_ID] = val; } + return val; } /** - *

- *

- *

- *

- * Implements cascade/inherit/important logic. This should result in the - * element for this style having a value for *each and every* (visual) - * property in the CSS2 spec. The implementation is based on the notion that + * This method should result in the element for this style having a + * derived value for all specified (in stylesheets, style attribute, other non + * CSS attrs, etc) primitive CSS properties. Other properties are picked up + * from an ancestor (if they inherit) or their initial values (if + * they don't inherit). See {@link #valueByName(CSSName)}. + *

+ * The implementation is based on the notion that * the matched styles are given to us in a perfectly sorted order, such that * properties appearing later in the rule-set always override properties - * appearing earlier. It also assumes that all properties in the CSS2 spec - * are defined somewhere across all the matched styles; for example, that - * the full-property set is given in the user-agent CSS that is always - * loaded with styles. The current implementation makes no attempt to check - * either of these assumptions. When this method exits, the derived property + * appearing earlier. + *

+ * The current implementation makes no attempt to check + * this assumption. When this method exits, the derived property * list for this class will be populated with the properties defined for - * this element, properly cascaded.

- * - * @param matched PARAM + * this element, properly cascaded. */ private void derive(CascadedStyle matched) { if (matched == null) { return; - }//nothing to derive + } - for (PropertyDeclaration pd : matched.getCascadedPropertyDeclarations()) { + for (PropertyDeclaration pd : matched.getCascadedPropertyDeclarations()) { FSDerivedValue val = deriveValue(pd.getCSSName(), pd.getValue()); _derivedValuesById[pd.getCSSName().FS_ID] = val; } @@ -1083,6 +1109,13 @@ public boolean isAvoidPageBreakInside() { return isIdent(CSSName.PAGE_BREAK_INSIDE, IdentValue.AVOID); } + /** + * This method derives a style for an anonymous child box with an overriden + * value for the display property. + *

+ * NOTE: All non-inherited properties of this will be lost as + * the returned style is for a child box. + */ public CalculatedStyle createAnonymousStyle(IdentValue display) { return deriveStyle(CascadedStyle.createAnonymousStyle(display)); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/derived/DerivedValueFactory.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/derived/DerivedValueFactory.java index 0be76289c..d0386b608 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/derived/DerivedValueFactory.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/derived/DerivedValueFactory.java @@ -28,6 +28,10 @@ import com.openhtmltopdf.css.style.FSDerivedValue; public class DerivedValueFactory { + /** + * Derived value factory. Requires a reference to a style so that it + * can resolve explicit inherit values with values from an ancestor. + */ public static FSDerivedValue newDerivedValue( CalculatedStyle style, CSSName cssName, PropertyValue value) { if (value.getCssValueType() == CSSValue.CSS_INHERIT) { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java index bf298b38e..aa65b26c1 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -150,7 +151,7 @@ public static void createChildren(LayoutContext c, BlockBox parent) { // The following is very useful for debugging. // It shows the contents of the box tree before layout. -// if (parent.getElement().getNodeName().equals("html")) { +// if (parent == c.getRootLayer().getMaster()) { // System.out.println(com.openhtmltopdf.util.LambdaUtil.descendantDump(parent)); // } } @@ -982,19 +983,19 @@ private static void insertGeneratedContent( if (contentDecl != null || counterResetDecl != null || counterIncrDecl != null) { calculatedStyle = parentStyle.deriveStyle(peStyle); - if (calculatedStyle.isDisplayNone()) return; - if (calculatedStyle.isIdent(CSSName.CONTENT, IdentValue.NONE)) return; - if (calculatedStyle.isIdent(CSSName.CONTENT, IdentValue.NORMAL) && (peName.equals("before") || peName.equals("after"))) + + if (calculatedStyle.isDisplayNone() || + calculatedStyle.isIdent(CSSName.CONTENT, IdentValue.NONE) || + (calculatedStyle.isIdent(CSSName.CONTENT, IdentValue.NORMAL) && + (peName.equals("before") || peName.equals("after")))) { return; + } - if (calculatedStyle.isTable() || calculatedStyle.isTableRow() || calculatedStyle.isTableSection()) { - CascadedStyle newPeStyle = - CascadedStyle.createLayoutStyle(peStyle, new PropertyDeclaration[] { - CascadedStyle.createLayoutPropertyDeclaration( - CSSName.DISPLAY, - IdentValue.BLOCK), - }); - calculatedStyle = parentStyle.deriveStyle(newPeStyle); + if (calculatedStyle.isTable() || + calculatedStyle.isTableRow() || + calculatedStyle.isTableSection()) { + + calculatedStyle = parentStyle.createAnonymousStyle(IdentValue.BLOCK); } c.resolveCounters(calculatedStyle); @@ -1107,7 +1108,11 @@ private static List wrapGeneratedContent(Element element, String peNa InlineBox iB = (InlineBox) i.next(); iB.setStyle(anon); iB.applyTextTransform(); - iB.setElement(null); + + if (iB.getElement() == null || + !"fs-footnote-marker".equals(iB.getElement().getNodeName())) { + iB.setElement(null); + } } BlockBox result = createBlockBox(style, info, true); @@ -1257,7 +1262,8 @@ private static void createElementChild( Node working, List children, ChildBoxInfo info, - CreateChildrenContext context) { + CreateChildrenContext context, + boolean allowFootnotes) { Styleable child = null; SharedContext sharedContext = c.getSharedContext(); @@ -1289,7 +1295,7 @@ private static void createElementChild( return; } - if (style.isFootnote()) { + if (style.isFootnote() && allowFootnotes) { c.setFootnoteIndex(c.getFootnoteIndex() + 1); // This is the official marker content that can generate zero or more boxes @@ -1304,91 +1310,119 @@ private static void createElementChild( iB.setStartsHere(true); iB.setEndsHere(true); iB.setFootnote(footnoteBody); + children.add(iB); + return; + } - } else if (style.isInline()) { + if (style.isInline()) { + createInlineChildren(c, parent, children, info, context, element); + } else { + child = createChildBlockBox(c, info, element, style); + } - if (context.needStartText) { - context.needStartText = false; - InlineBox iB = createInlineBox("", parent, context.parentStyle, null); - iB.setStartsHere(true); - iB.setEndsHere(false); - children.add(iB); - context.previousIB = iB; - } + if (child != null) { + children.add(child); + } + } - createChildren(c, null, element, children, info, true); + private static void createInlineChildren( + LayoutContext c, + Element parent, + List children, + ChildBoxInfo info, + CreateChildrenContext context, + Element element) { - if (context.inline) { - if (context.previousIB != null) { - context.previousIB.setEndsHere(false); - } - context.needEndText = true; - } - } else { - if (style.hasColumns() && c.isPrint()) { - child = new FlowingColumnContainerBox(); - } else { - child = createBlockBox(style, info, false); - } + if (context.needStartText) { + context.needStartText = false; + InlineBox iB = createInlineBox("", parent, context.parentStyle, null); + iB.setStartsHere(true); + iB.setEndsHere(false); + children.add(iB); + context.previousIB = iB; + } - child.setStyle(style); - child.setElement(element); + createChildren(c, null, element, children, info, true); - if (style.hasColumns() && c.isPrint()) { - createColumnContainer(c, child, element, style); + if (context.inline) { + if (context.previousIB != null) { + context.previousIB.setEndsHere(false); } + context.needEndText = true; + } + } - if (style.isListItem()) { - BlockBox block = (BlockBox) child; - block.setListCounter(c.getCounterContext(style).getCurrentCounterValue("list-item")); - } + private static Styleable createChildBlockBox( + LayoutContext c, ChildBoxInfo info, Element element, CalculatedStyle style) { - if (style.isTable() || style.isInlineTable()) { - TableBox table = (TableBox) child; - table.ensureChildren(c); + Styleable child; - child = reorderTableContent(c, table); - } + if (style.hasColumns() && c.isPrint()) { + child = new FlowingColumnContainerBox(); + } else { + child = createBlockBox(style, info, false); + } - if (!info.isContainsBlockLevelContent() - && !style.isLayedOutInInlineContext()) { - info.setContainsBlockLevelContent(true); - } + child.setStyle(style); + child.setElement(element); + + if (style.hasColumns() && c.isPrint()) { + createColumnContainer(c, child, element, style); + } + if (style.isListItem()) { BlockBox block = (BlockBox) child; + block.setListCounter(c.getCounterContext(style).getCurrentCounterValue("list-item")); + } - if (block.getStyle().mayHaveFirstLine()) { - block.setFirstLineStyle(c.getCss().getPseudoElementStyle(element, - "first-line")); - } - if (block.getStyle().mayHaveFirstLetter()) { - block.setFirstLetterStyle(c.getCss().getPseudoElementStyle(element, - "first-letter")); - } + if (style.isTable() || style.isInlineTable()) { + TableBox table = (TableBox) child; + table.ensureChildren(c); - // I think we need to do this to evaluate counters correctly - block.ensureChildren(c); + child = reorderTableContent(c, table); } - if (child != null) { - children.add(child); + if (!info.isContainsBlockLevelContent() + && !style.isLayedOutInInlineContext()) { + info.setContainsBlockLevelContent(true); + } + + BlockBox block = (BlockBox) child; + + if (block.getStyle().mayHaveFirstLine()) { + block.setFirstLineStyle(c.getCss().getPseudoElementStyle(element, + "first-line")); + } + if (block.getStyle().mayHaveFirstLetter()) { + block.setFirstLetterStyle(c.getCss().getPseudoElementStyle(element, + "first-letter")); } + + // I think we need to do this to evaluate counters correctly + block.ensureChildren(c); + return child; } /** * Creates the footnote body to put at the bottom of the page inside a * page's footnote area. */ - private static BlockBox createFootnoteBody(LayoutContext c, Element element, CalculatedStyle style) { + private static BlockBox createFootnoteBody( + LayoutContext c, Element element, CalculatedStyle style) { + List footnoteChildren = new ArrayList<>(); ChildBoxInfo footnoteChildInfo = new ChildBoxInfo(); // Create the out-of-flow footnote-body box as a block box. BlockBox footnoteBody = new BlockBox(); - CalculatedStyle footnoteBodyStyle = style.createAnonymousStyle(IdentValue.BLOCK); + CalculatedStyle footnoteBodyStyle = new EmptyStyle().createAnonymousStyle(IdentValue.BLOCK); + + // Create a dummy element for the footnote-body. + Element fnBodyElement = element.getOwnerDocument().createElement("fs-footnote-body"); + c.getRootLayer().getMaster().getElement().appendChild(fnBodyElement); - footnoteBody.setElement(element); + footnoteBody.setElement(fnBodyElement); footnoteBody.setStyle(footnoteBodyStyle); // This will be set to the same as the footnote area when we add it to the page. @@ -1403,13 +1437,16 @@ private static BlockBox createFootnoteBody(LayoutContext c, Element element, Cal c.pushLayer(layer); - // The footnote marker followed by footnote element children. - insertGeneratedContent(c, element, footnoteBodyStyle, "footnote-marker", footnoteChildren, footnoteChildInfo); - createChildren(c, footnoteBody, element, footnoteChildren, footnoteChildInfo, footnoteBodyStyle.isInline()); + CreateChildrenContext context = new CreateChildrenContext(false, false, style.getParent(), false); + createElementChild(c, (Element) element.getParentNode(), footnoteBody, element, footnoteChildren, footnoteChildInfo, context, false); resolveChildren(c, footnoteBody, footnoteChildren, footnoteChildInfo); c.popLayer(); +// System.out.println(); +// System.out.println(com.openhtmltopdf.util.LambdaUtil.descendantDump(footnoteBody)); +// System.out.println(); + return footnoteBody; } @@ -1450,6 +1487,10 @@ private static void createChildren( insertGeneratedContent(c, parent, parentStyle, "before", children, info); + if (parentStyle.isFootnote()) { + insertGeneratedContent(c, parent, parentStyle, "footnote-marker", children, info); + } + Node working = parent.getFirstChild(); CreateChildrenContext context = null; @@ -1461,7 +1502,7 @@ private static void createChildren( if (nodeType == Node.ELEMENT_NODE) { createElementChild( - c, parent, blockParent, working, children, info, context); + c, parent, blockParent, working, children, info, context, true); } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) { context.needStartText = false; context.needEndText = false; diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/SharedContext.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/SharedContext.java index 5da3ab2f8..66bd10393 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/SharedContext.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/SharedContext.java @@ -96,7 +96,7 @@ public class SharedContext { private float mmPerDot; private boolean print; - private Map styleMap; + private final Map styleMap = new HashMap<>(1024, 0.75f); private ReplacedElementFactory replacedElementFactory; private Rectangle tempCanvas; @@ -465,29 +465,25 @@ public void setDotsPerPixel(int dotsPerPixel) { this.dotsPerPixel = dotsPerPixel; } + /** + * Gets the resolved style for an element. All primitive properties will + * have values. + *

+ * This method uses a cache. + *

+ * If the parent element's style is not cached this method will recursively + * work up the ancestor list until it styles the document with the initial values + * of CSS properties. + */ public CalculatedStyle getStyle(Element e) { - return getStyle(e, false); - } + CalculatedStyle result = styleMap.get(e); - public CalculatedStyle getStyle(Element e, boolean restyle) { - if (styleMap == null) { - styleMap = new HashMap<>(1024, 0.75f); - } - - CalculatedStyle result = null; - if (! restyle) { - result = styleMap.get(e); - } if (result == null) { Node parent = e.getParentNode(); - CalculatedStyle parentCalculatedStyle; - if (parent instanceof Document) { - parentCalculatedStyle = new EmptyStyle(); - } else { - parentCalculatedStyle = getStyle((Element)parent, false); - } - - result = parentCalculatedStyle.deriveStyle(getCss().getCascadedStyle(e, restyle)); + CalculatedStyle parentCalculatedStyle = parent instanceof Document ? + new EmptyStyle() : getStyle((Element) parent); + + result = parentCalculatedStyle.deriveStyle(getCss().getCascadedStyle(e, false)); styleMap.put(e, result); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java index 99488720a..90c0233e3 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java @@ -1053,58 +1053,6 @@ protected void moveIfGreater(Dimension result, Dimension test) { } } - public void restyle(LayoutContext c) { - Element e = getElement(); - CalculatedStyle style = null; - - String pe = getPseudoElementOrClass(); - if (pe != null) { - if (e != null) { - style = c.getSharedContext().getStyle(e, true); - style = style.deriveStyle(c.getCss().getPseudoElementStyle(e, pe)); - } else { - BlockBox container = (BlockBox)getParent().getParent(); - e = container.getElement(); - style = c.getSharedContext().getStyle(e, true); - style = style.deriveStyle(c.getCss().getPseudoElementStyle(e, pe)); - style = style.createAnonymousStyle(IdentValue.INLINE); - } - } else { - if (e != null) { - style = c.getSharedContext().getStyle(e, true); - if (isAnonymous()) { - style = style.createAnonymousStyle(getStyle().getIdent(CSSName.DISPLAY)); - } - } else { - Box parent = getParent(); - if (parent != null) { - e = parent.getElement(); - if (e != null) { - style = c.getSharedContext().getStyle(e, true); - style = style.createAnonymousStyle(IdentValue.INLINE); - } - } - } - } - - if (style != null) { - setStyle(style); - } - - restyleChildren(c); - } - - protected void restyleChildren(LayoutContext c) { - for (int i = 0; i < getChildCount(); i++) { - Box b = getChild(i); - b.restyle(c); - } - } - - public Box getRestyleTarget() { - return this; - } - /** * The zero based index of this child amongst its fellow children of its parent. */ diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java index 0e7acf339..48aad8614 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java @@ -512,7 +512,7 @@ public List getElementWithContent() { List result = new ArrayList<>(); - BlockBox container = (BlockBox)getLineBox().getParent(); + BlockBox container = getLineBox().getParent(); while (true) { List elementBoxes = container.getElementBoxes(getElement()); for (int i = 0; i < elementBoxes.size(); i++) { @@ -876,31 +876,7 @@ public String dump(LayoutContext c, String indent, int which) { return result.toString(); } - - @Override - public void restyle(LayoutContext c) { - super.restyle(c); - calculateTextDecoration(c); - } - - @Override - protected void restyleChildren(LayoutContext c) { - for (int i = 0; i < getInlineChildCount(); i++) { - Object obj = getInlineChild(i); - if (obj instanceof Box) { - ((Box)obj).restyle(c); - } - } - } - - @Override - public Box getRestyleTarget() { - // Inline boxes may be broken across lines so back out - // to the nearest block box - Box result = findAncestor(bx -> !(bx instanceof InlineLayoutBox)); - return result.getParent(); - } - + @Override public void collectText(RenderingContext c, StringBuilder buffer) { for (Object obj : getInlineChildren()) { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java index b97992697..31b4b549d 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java @@ -27,13 +27,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.w3c.dom.Element; import com.openhtmltopdf.bidi.BidiSplitter; import com.openhtmltopdf.css.constants.CSSName; import com.openhtmltopdf.css.constants.IdentValue; import com.openhtmltopdf.css.parser.FSRGBColor; -import com.openhtmltopdf.css.style.CalculatedStyle; import com.openhtmltopdf.css.style.CssContext; import com.openhtmltopdf.extend.StructureType; import com.openhtmltopdf.layout.BoxCollector; @@ -611,24 +609,7 @@ public boolean isContainsOnlyBlockLevelContent() { return true; } - - @Override - public Box getRestyleTarget() { - return getParent(); - } - - @Override - public void restyle(LayoutContext c) { - Box parent = getParent(); - Element e = parent.getElement(); - if (e != null) { - CalculatedStyle style = c.getSharedContext().getStyle(e, true); - setStyle(style.createAnonymousStyle(IdentValue.BLOCK)); - } - - restyleChildren(c); - } - + public boolean isContainsVisibleContent() { for (int i = 0; i < getChildCount(); i++) { Box b = getChild(i); diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-364-footnotes-blocks.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-364-footnotes-blocks.html index 29a99a9b9..448e79cbe 100644 --- a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-364-footnotes-blocks.html +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-364-footnotes-blocks.html @@ -26,6 +26,8 @@ color: blue; content: counter(footnote) ". "; float: left; + width: 1.5em; + border: 1px solid orange; } .footnote { clear: both; @@ -52,7 +54,7 @@
-

+

Footnote with large text!

diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index bf5b70a86..764d96a65 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -1441,8 +1441,13 @@ public void testIssue364FootnotesTooLarge() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-too-large")); } + /** + * Tests that we support display: block elements in the footnote area + * and that blocks with page-break-inside: avoid do not intersect + * with the footnote area. + */ @Test - @Ignore // Failing badly. + @Ignore // Working well. public void testIssue364FootnotesBlocks() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-blocks")); }