Skip to content

Commit 992c837

Browse files
committed
[RELEASE] iText 7 pdfHTML - 3.0.1
https://github.com/itext/i7j-pdfhtml/releases/tag/3.0.1 * release/3.0.1: [RELEASE] 3.0.1-SNAPSHOT -> 3.0.1 Fix issues with number filtering for <input type="number"> Update cmp files Remove closed TODO remarks. Add ticket references for unresolved TODOs Refactor tests since zero or negative size log message level changed to INFO Fix problem with NullPointerException in ListStyleApplierUtil class and add invalidLinearGradientTest Add logic of parsing linear-gradient in CSS 'content' property Pass svg root renderer to SvgDrawContext Add PdfDocument as an argument for build linear gradient Add new tests for border collapse in table elements Add shorthand text decoration support Add integration tests for CSS linear-gradient background-image for different HTML elements Tests update Updates after review Add tests for TR's border style Add missing copyright headers Test update Add linear gradient support to ListStyleApplierUtil class Add tests for the linear gradient in list-style-image property Tests update and add test with span element with image background Add test reproducing the exception on rendering table inside of page margin Add missing copyright headers Linear Gradient support for background image Minor fix in a comment Add DEVSIX link in TODO Specify ticket IDs for several TODOs Update .mailmap Fix Javadoc warning and a typo in the method description Update cmp files Update some KEEP_TOGETHER related cmps Add some new tagging tests. Update some cmps Add missing copyright headers Improve detection of whether pages counter reference is present in the stylesheet Change RND to DEVSIX task and delete closed DEVSIX Move TableAlignmentIssuesTest from samples internal repository Add test on font weight style applying to anonymous paragraphs Delete document font size setting in BodyTagWorker constructor and move runApplier method Add extending from the ExtendedITextTest to OrphansWidowsUnitTest class Add tests for text-decoration shorthand CSS property Add tests for line-height property in span elements [RELEASE] Update dependency versions Refine TODO regarding incomplete support for absolute positioning for images
2 parents 8edebb2 + a1f8a4f commit 992c837

File tree

408 files changed

+3895
-673
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

408 files changed

+3895
-673
lines changed

.mailmap

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ Peter Goodman <[email protected]> <[email protected]
6464
6565
6666
67-
6867
6968
7069

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
<parent>
66
<groupId>com.itextpdf</groupId>
77
<artifactId>root</artifactId>
8-
<version>7.1.11</version>
8+
<version>7.1.12</version>
99
<relativePath/>
1010
</parent>
1111

1212
<artifactId>html2pdf</artifactId>
13-
<version>3.0.0</version>
13+
<version>3.0.1</version>
1414

1515
<name>pdfHTML</name>
1616
<description>pdfHTML is an iText 7 add-on that lets you to parse (X)HTML snippets and the associated CSS and converts

src/main/java/com/itextpdf/html2pdf/LogMessageConstant.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public final class LogMessageConstant {
7373
public static final String INPUT_TYPE_IS_NOT_SUPPORTED = "Input type {0} is not supported";
7474
/** The Constant INVALID_CSS_PROPERTY_DECLARATION. */
7575
public static final String INVALID_CSS_PROPERTY_DECLARATION = "Invalid css property declaration: {0}";
76+
/** The Constant INVALID_CSS_PROPERTY_DECLARATION. */
77+
public static final String INVALID_GRADIENT_DECLARATION = "Invalid gradient declaration: {0}";
7678
/** The Constant MARGIN_VALUE_IN_PERCENT_NOT_SUPPORTED. */
7779
public static final String MARGIN_VALUE_IN_PERCENT_NOT_SUPPORTED = "Margin value in percents not supported";
7880
/** The Constant NOT_SUPPORTED_LIST_STYLE_TYPE. */
@@ -99,6 +101,8 @@ public final class LogMessageConstant {
99101
public static final String QUOTES_PROPERTY_INVALID = "Quote property \"{0}\" is invalid. It should contain even number of <string> values.";
100102
/** The Constant TEXT_DECORATION_BLINK_NOT_SUPPORTED. */
101103
public static final String TEXT_DECORATION_BLINK_NOT_SUPPORTED = "text-decoration: blink not supported";
104+
/** The Constant HSL_COLOR_NOT_SUPPORTED. */
105+
public static final String HSL_COLOR_NOT_SUPPORTED = "Hsl colors are not supported";
102106
/** The Constant UNABLE_TO_PROCESS_EXTERNAL_CSS_FILE. */
103107
public static final String UNABLE_TO_PROCESS_EXTERNAL_CSS_FILE = "Unable to process external css file";
104108
/** The Constant UNABLE_TO_PROCESS_IMAGE_AS_SVG */

src/main/java/com/itextpdf/html2pdf/attach/impl/DefaultHtmlProcessor.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,14 @@ private void visit(INode node) {
350350
((HtmlTagWorker) tagWorker).processPageRules(node, cssResolver, context);
351351
}
352352

353-
context.getOutlineHandler().addOutlineAndDestToDocument(tagWorker, element, context);
353+
if (TagConstants.BODY.equals(element.name()) || TagConstants.HTML.equals(element.name())) {
354+
runApplier(element, tagWorker);
355+
}
354356

357+
context.getOutlineHandler().addOutlineAndDestToDocument(tagWorker, element, context);
355358

356359
visitPseudoElement(element, tagWorker, CssConstants.BEFORE);
357360
visitPseudoElement(element, tagWorker, CssConstants.PLACEHOLDER);
358-
if (TagConstants.BODY.equals(element.name()) || TagConstants.HTML.equals(element.name()))
359-
runApplier(element, tagWorker);
360361
for (INode childNode : element.childNodes()) {
361362
if (!context.isProcessingInlineSvg()) {
362363
visit(childNode);

src/main/java/com/itextpdf/html2pdf/attach/impl/DefaultTagWorkerMapping.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ class DefaultTagWorkerMapping {
223223
workerMapping.putMapping(beforePseudoElemName, CssConstants.TABLE, DivTagWorker.class);
224224
workerMapping.putMapping(afterPseudoElemName, CssConstants.TABLE, DivTagWorker.class);
225225
workerMapping.putMapping(CssPseudoElementUtil.createPseudoElementTagName(TagConstants.IMG), ImgTagWorker.class);
226+
workerMapping.putMapping(CssPseudoElementUtil.createPseudoElementTagName(TagConstants.DIV), DivTagWorker.class);
226227

227228
// custom elements mapping, implementation-specific
228229
workerMapping.putMapping(PageCountElementNode.PAGE_COUNTER_TAG, PageCountWorker.class);

src/main/java/com/itextpdf/html2pdf/attach/impl/layout/HtmlDocumentRenderer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public HtmlDocumentRenderer(Document document, boolean immediateFlush) {
143143
public void processPageRules(INode rootNode, ICssResolver cssResolver, ProcessorContext context) {
144144
PageContextProperties firstPageProps = PageContextProperties.resolve(rootNode, cssResolver, context.getCssContext(),
145145
PageContextConstants.FIRST, PageContextConstants.RIGHT);
146-
// TODO in documents with set to rtl on root document, first page is considered as left
146+
// TODO DEVSIX-4118 in documents with set to rtl on root document, first page is considered as left
147147
PageContextProperties leftPageProps = PageContextProperties.resolve(rootNode, cssResolver, context.getCssContext(),
148148
PageContextConstants.LEFT);
149149
PageContextProperties rightPageProps = PageContextProperties.resolve(rootNode, cssResolver, context.getCssContext(),
@@ -462,7 +462,7 @@ private PageContextProcessor getPageProcessor(int pageNum) {
462462
* @return true, if is current page left
463463
*/
464464
private boolean isPageLeft(int pageNum) {
465-
// TODO rtl
465+
// TODO DEVSIX-4118 rtl
466466
boolean pageIsEven = pageNum % 2 == 0;
467467
return evenPagesAreLeft == pageIsEven;
468468
}

src/main/java/com/itextpdf/html2pdf/attach/impl/layout/PageMarginBoxBuilder.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ This file is part of the iText (R) project.
4747
import com.itextpdf.html2pdf.attach.ProcessorContext;
4848
import com.itextpdf.html2pdf.css.apply.ICssApplier;
4949
import com.itextpdf.html2pdf.css.page.PageMarginRunningElementNode;
50+
import com.itextpdf.html2pdf.css.resolve.DefaultCssResolver;
5051
import com.itextpdf.kernel.geom.PageSize;
5152
import com.itextpdf.kernel.geom.Rectangle;
5253
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -64,6 +65,7 @@ This file is part of the iText (R) project.
6465
import com.itextpdf.styledxmlparser.css.util.CssUtils;
6566
import com.itextpdf.styledxmlparser.node.IElementNode;
6667
import com.itextpdf.styledxmlparser.node.INode;
68+
import com.itextpdf.styledxmlparser.node.IStylesContainer;
6769
import com.itextpdf.styledxmlparser.node.ITextNode;
6870
import org.slf4j.LoggerFactory;
6971

@@ -133,6 +135,7 @@ public PageMarginBoxContextNode[] getNodes() {
133135
private IElement processMarginBoxContent(PageMarginBoxContextNode marginBoxContentNode, int pageNumber, ProcessorContext context) {
134136
IElementNode dummyMarginBoxNode = new PageMarginBoxDummyElement();
135137
dummyMarginBoxNode.setStyles(marginBoxContentNode.getStyles());
138+
DefaultCssResolver cssResolver = new DefaultCssResolver(marginBoxContentNode, context);
136139
ITagWorker marginBoxWorker = context.getTagWorkerFactory().getTagWorker(dummyMarginBoxNode, context);
137140
for (int i = 0; i < marginBoxContentNode.childNodes().size(); i++) {
138141
INode childNode = marginBoxContentNode.childNodes().get(i);
@@ -142,6 +145,13 @@ private IElement processMarginBoxContent(PageMarginBoxContextNode marginBoxConte
142145
} else if (childNode instanceof IElementNode) {
143146
ITagWorker childTagWorker = context.getTagWorkerFactory().getTagWorker((IElementNode) childNode, context);
144147
if (childTagWorker != null) {
148+
Map<String, String> stringStringMap = cssResolver.resolveStyles(childNode, context.getCssContext());
149+
((IElementNode) childNode).setStyles(stringStringMap);
150+
ICssApplier cssApplier = context.getCssApplierFactory().getCssApplier((IElementNode) childNode);
151+
if (cssApplier != null) {
152+
cssApplier.apply(context, (IStylesContainer) childNode, childTagWorker);
153+
}
154+
145155
childTagWorker.processEnd((IElementNode) childNode, context);
146156
marginBoxWorker.processTagChild(childTagWorker, context);
147157
}
@@ -584,7 +594,7 @@ private float[] calculateDistribution(float argA, float argC, float flexA, float
584594
* @param dim Dimension container containing min and max dimension info
585595
* @param dimensions array of calculated auto values for boxes in the given dimension
586596
* @param index position in the array to look at
587-
* @return True if the values in dimensions trigger a recalculation, false otherwise
597+
* @return <code>true</code> if the values in dimensions trigger a recalculation, <code>false</code> otherwise
588598
*/
589599
private boolean recalculateIfNecessary(DimensionContainer dim, float[] dimensions, int index) {
590600
if (dim != null) {
@@ -603,7 +613,7 @@ private boolean recalculateIfNecessary(DimensionContainer dim, float[] dimension
603613
/**
604614
* Calculate the margin boxes given the list of margin boxes that have generated content
605615
*
606-
* @return Rectangle[12] containing the calulated bounding boxes of the margin-box-nodes. Rectangles with 0 width and/or heigh
616+
* @return Rectangle[12] containing the calculated bounding boxes of the margin-box-nodes. Rectangles with 0 width and/or height
607617
* refer to empty boxes. The order is TLC(top-left-corner)-TL-TC-TY-TRC-RT-RM-RB-RBC-BR-BC-BL-BLC-LB-LM-LT
608618
*/
609619
private Rectangle[] calculateMarginBoxRectanglesCornersOnly() {

src/main/java/com/itextpdf/html2pdf/attach/impl/tags/BodyTagWorker.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ This file is part of the iText (R) project.
5252
import com.itextpdf.layout.IPropertyContainer;
5353
import com.itextpdf.layout.element.Div;
5454
import com.itextpdf.layout.element.IElement;
55-
import com.itextpdf.layout.property.Property;
56-
import com.itextpdf.layout.property.UnitValue;
57-
import com.itextpdf.html2pdf.css.CssConstants;
5855
import com.itextpdf.layout.tagging.IAccessibleElement;
59-
import com.itextpdf.styledxmlparser.css.util.CssUtils;
6056
import com.itextpdf.styledxmlparser.node.IElementNode;
6157

6258
/**
@@ -79,15 +75,6 @@ public class BodyTagWorker extends DivTagWorker {
7975
public BodyTagWorker(IElementNode element, ProcessorContext context) {
8076
super(element, context);
8177
parentTagWorker = context.getState().empty() ? null : context.getState().top();
82-
if (parentTagWorker != null && parentTagWorker.getElementResult() != null) {
83-
// TODO this is not in css applier because css applier is called after the elements are added to the document
84-
// We need to apply font styles here specifically to set font-size to the document because this is needed for
85-
// inline-blocks with fixed height when the height is smaller than the defined font size
86-
float em = CssUtils.parseAbsoluteLength(element.getStyles().get(CssConstants.FONT_SIZE));
87-
if (em != 0) {
88-
parentTagWorker.getElementResult().setProperty(Property.FONT_SIZE, UnitValue.createPointValue(em));
89-
}
90-
}
9178

9279
PdfDocument pdfDocument = context.getPdfDocument();
9380
if (pdfDocument != null) {

src/main/java/com/itextpdf/html2pdf/attach/impl/tags/ImgTagWorker.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ public ImgTagWorker(IElementNode element, ProcessorContext context) {
9696
}
9797

9898
display = element.getStyles() != null ? element.getStyles().get(CssConstants.DISPLAY) : null;
99-
// TODO this is a workaround for now Imgto that image is not added as inline
10099
if (element.getStyles() != null && CssConstants.ABSOLUTE.equals(element.getStyles().get(CssConstants.POSITION))) {
100+
// TODO DEVSIX-1393: we don't support absolute positioning in inline context.
101+
// This workaround allows to identify image as an element which needs to be processed outside of inline context.
102+
// See AbsoluteReplacedHeight001Test.
101103
display = CssConstants.BLOCK;
102104
}
103105

src/main/java/com/itextpdf/html2pdf/attach/impl/tags/InputTagWorker.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ This file is part of the iText (R) project.
5858
import com.itextpdf.html2pdf.html.AttributeConstants;
5959
import com.itextpdf.styledxmlparser.css.util.CssUtils;
6060
import com.itextpdf.styledxmlparser.node.IElementNode;
61+
62+
import java.util.regex.Pattern;
6163
import org.slf4j.Logger;
6264
import org.slf4j.LoggerFactory;
6365

@@ -66,6 +68,9 @@ This file is part of the iText (R) project.
6668
*/
6769
public class InputTagWorker implements ITagWorker, IDisplayAware {
6870

71+
private static final Pattern NUMBER_INPUT_ALLOWED_VALUES =
72+
Pattern.compile("^(((-?[0-9]+)(\\.[0-9]+)?)|(-?\\.[0-9]+))$");
73+
6974
/**
7075
* The form element.
7176
*/
@@ -190,8 +195,9 @@ public IPropertyContainer getElementResult() {
190195
return formElement;
191196
}
192197

193-
private static String preprocessInputValue(String value, String inputType) {
194-
if (AttributeConstants.NUMBER.equals(inputType) && value != null && !value.matches("[0-9.]*")) {
198+
static String preprocessInputValue(String value, String inputType) {
199+
if (AttributeConstants.NUMBER.equals(inputType) && value != null &&
200+
!NUMBER_INPUT_ALLOWED_VALUES.matcher(value).matches()) {
195201
value = "";
196202
}
197203
return value;

src/main/java/com/itextpdf/html2pdf/css/CssConstants.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ public class CssConstants extends CommonCssConstants {
7575
/** The Constant DISPLAY. */
7676
public static final String DISPLAY = "display";
7777

78-
/** The Constant HEIGHT. */
79-
public static final String HEIGHT = "height";
80-
8178
/** The Constant MARKS. */
8279
public static final String MARKS = "marks";
8380

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2020 iText Group NV
4+
Authors: Bruno Lowagie, Paulo Soares, et al.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.html2pdf.css.apply.impl;
44+
45+
import com.itextpdf.html2pdf.attach.ITagWorker;
46+
import com.itextpdf.html2pdf.attach.ProcessorContext;
47+
import com.itextpdf.html2pdf.css.CssConstants;
48+
import com.itextpdf.html2pdf.css.apply.ICssApplier;
49+
import com.itextpdf.html2pdf.css.apply.util.BackgroundApplierUtil;
50+
import com.itextpdf.html2pdf.css.apply.util.WidthHeightApplierUtil;
51+
import com.itextpdf.layout.IPropertyContainer;
52+
import com.itextpdf.layout.element.Div;
53+
import com.itextpdf.styledxmlparser.node.IStylesContainer;
54+
55+
import java.util.Map;
56+
57+
/**
58+
* {@link ICssApplier} implementation for linear-gradient elements in content CSS property.
59+
*/
60+
public class CssContentLinearGradientApplier implements ICssApplier {
61+
/** The default width of the div content in the points, and this will be 300 pixels. */
62+
private static final float DEFAULT_CONTENT_WIDTH_PT = 225;
63+
64+
/** The default height of the div content in the points, and this will be 150 pixels. */
65+
private static final float DEFAULT_CONTENT_HEIGHT_PT = 112.5f;
66+
67+
/* (non-Javadoc)
68+
* @see com.itextpdf.html2pdf.css.apply.ICssApplier#apply(com.itextpdf.html2pdf.attach.ProcessorContext, com.itextpdf.html2pdf.html.node.IStylesContainer, com.itextpdf.html2pdf.attach.ITagWorker)
69+
*/
70+
@Override
71+
public void apply(ProcessorContext context, IStylesContainer stylesContainer, ITagWorker tagWorker) {
72+
Map<String, String> cssProps = stylesContainer.getStyles();
73+
74+
IPropertyContainer container = tagWorker.getElementResult();
75+
if (container != null) {
76+
if (container instanceof Div) {
77+
if (!cssProps.containsKey(CssConstants.WIDTH) || CssConstants.AUTO.equals(cssProps.get(CssConstants.WIDTH))) {
78+
((Div) container).setWidth(DEFAULT_CONTENT_WIDTH_PT);
79+
}
80+
if (!cssProps.containsKey(CssConstants.HEIGHT) || CssConstants.AUTO.equals(cssProps.get(CssConstants.HEIGHT))) {
81+
((Div) container).setHeight(DEFAULT_CONTENT_HEIGHT_PT);
82+
}
83+
}
84+
85+
WidthHeightApplierUtil.applyWidthHeight(cssProps, context, container);
86+
BackgroundApplierUtil.applyBackground(cssProps, context, container);
87+
}
88+
}
89+
}

src/main/java/com/itextpdf/html2pdf/css/apply/impl/DefaultTagCssApplierMapping.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ private DefaultTagCssApplierMapping() {
181181
mapping.putMapping(beforePseudoElemName, CssConstants.TABLE, BlockCssApplier.class);
182182
mapping.putMapping(afterPseudoElemName, CssConstants.TABLE, BlockCssApplier.class);
183183
mapping.putMapping(CssPseudoElementUtil.createPseudoElementTagName(TagConstants.IMG), BlockCssApplier.class);
184+
mapping.putMapping(CssPseudoElementUtil.createPseudoElementTagName(TagConstants.DIV), CssContentLinearGradientApplier.class);
184185

185186

186187
// custom elements mapping, implementation-specific

src/main/java/com/itextpdf/html2pdf/css/apply/impl/SpanTagCssApplier.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ public void apply(ProcessorContext context, IStylesContainer stylesContainer, IT
106106
}
107107
}
108108

109-
for (IPropertyContainer child : spanTagWorker.getAllElements()) {
110-
FloatPropertyValue kidFloatVal = child.<FloatPropertyValue>getProperty(Property.FLOAT);
111-
if (child instanceof Text && !child.hasOwnProperty(Property.BACKGROUND)
112-
&& (kidFloatVal == null || FloatPropertyValue.NONE.equals(kidFloatVal))) {
113-
BackgroundApplierUtil.applyBackground(cssStyles, context, child);
109+
if (spanTagWorker.getAllElements() != null) {
110+
for (IPropertyContainer child : spanTagWorker.getAllElements()) {
111+
FloatPropertyValue kidFloatVal = child.<FloatPropertyValue>getProperty(Property.FLOAT);
112+
if (child instanceof Text && !child.hasOwnProperty(Property.BACKGROUND)
113+
&& (kidFloatVal == null || FloatPropertyValue.NONE.equals(kidFloatVal))) {
114+
BackgroundApplierUtil.applyBackground(cssStyles, context, child);
115+
}
114116
}
115117
}
116118
}

0 commit comments

Comments
 (0)