diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java index d26401cf6..43bb1e6de 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/Paragraph.java @@ -49,6 +49,7 @@ package com.fr.third.v2.lowagie.text; +import com.fr.third.v2.lowagie.text.html.BorderAttribute; import java.util.HashMap; /** @@ -110,6 +111,8 @@ public class Paragraph extends Phrase { protected HashMap attributes = new HashMap(); + private BorderAttribute borderAttr; + public HashMap getAttributes() { return attributes; } @@ -527,4 +530,12 @@ public class Paragraph extends Phrase { return this.background; } + public BorderAttribute getBorderAttr() { + return borderAttr; + } + + public void setBorderAttrs(BorderAttribute borderAttrs) { + this.borderAttr = borderAttrs; + } + } diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Border.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Border.java new file mode 100644 index 000000000..c4a355a58 --- /dev/null +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Border.java @@ -0,0 +1,39 @@ +package com.fr.third.v2.lowagie.text.html; + +import java.awt.Color; + +/** + * @author Hugh.C + * @version 1.0 + * Created by Hugh.C on 2020/6/15 + */ +public class Border { + private float width; + private String style; + private Color color = Color.BLACK; + + public float getWidth() { + return width; + } + + public void setWidth(float width) { + this.width = width; + } + + public String getStyle() { + return style; + } + + public void setStyle(String style) { + this.style = style; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + +} diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderAttribute.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderAttribute.java new file mode 100644 index 000000000..6960b9318 --- /dev/null +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderAttribute.java @@ -0,0 +1,106 @@ +package com.fr.third.v2.lowagie.text.html; + +import java.awt.Color; +import java.util.Map; + +/** + * @author Hugh.C + * @version 1.0 + * Created by Hugh.C on 2020/6/15 + */ +public class BorderAttribute { + protected Border top = new Border(); + protected Border right = new Border(); + protected Border bottom = new Border(); + protected Border left = new Border(); + + public Border getTop() { return top; } + + public Border getRight() { + return right; + } + + public Border getBottom() { + return bottom; + } + + public Border getLeft() { + return left; + } + + /** + * 将解析好的边框属性转化为 BorderAttribute + * + * @param map + * @return + */ + public BorderAttribute applyBorderAttr(Map map) { + if (null == map || map.isEmpty()) { + return this; + } + String topWidth = map.get(Markup.CSS_KEY_BORDERWIDTHTOP); + String style = map.get(Markup.CSS_KEY_BORDERSTYLETOP); + if (verify(topWidth, style)) { + Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORTOP)); + top.setWidth(CSSUtils.parseFloat(topWidth)); + top.setStyle(style); + top.setColor(null == color ? Color.BLACK : color); + } + String rightWidth = map.get(Markup.CSS_KEY_BORDERWIDTHRIGHT); + style = map.get(Markup.CSS_KEY_BORDERSTYLERIGHT); + if (verify(rightWidth, style)) { + Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORRIGHT)); + right.setWidth(CSSUtils.parseFloat(rightWidth)); + right.setStyle(style); + right.setColor(null == color ? Color.BLACK : color); + } + String bottomWidth = map.get(Markup.CSS_KEY_BORDERWIDTHBOTTOM); + style = map.get(Markup.CSS_KEY_BORDERSTYLEBOTTOM); + if (verify(bottomWidth, style)) { + Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORBOTTOM)); + bottom.setWidth(CSSUtils.parseFloat(bottomWidth)); + bottom.setStyle(style); + bottom.setColor(null == color ? Color.BLACK : color); + } + String leftWidth = map.get(Markup.CSS_KEY_BORDERWIDTHLEFT); + style = map.get(Markup.CSS_KEY_BORDERSTYLELEFT); + if (verify(leftWidth, style)) { + Color color = Markup.decodeColor(map.get(Markup.CSS_KEY_BORDERCOLORLEFT)); + left.setWidth(CSSUtils.parseFloat(leftWidth)); + left.setStyle(style); + left.setColor(null == color ? Color.BLACK : color); + } + return this; + } + + /** + * 将边框属性 拼接成 html style 中的值 + * + * @return + */ + public String toStyleAttr() { + StringBuffer sb = new StringBuffer(); + sb.append(border2StyleAttr(Markup.CSS_KEY_BORDERTOP, top)).append(border2StyleAttr(Markup.CSS_KEY_BORDERRIGHT, right)) + .append(border2StyleAttr(Markup.CSS_KEY_BORDERBOTTOM, bottom)).append(border2StyleAttr(Markup.CSS_KEY_BORDERLEFT, left)); + return sb.toString(); + } + + private String border2StyleAttr(String key, Border border) { + if (!verify(border)) { + return ""; + } + Color color = null == border.getColor() ? Color.BLACK : border.getColor(); + StringBuilder sb = new StringBuilder(); + sb.append(key).append(":").append(border.getWidth()).append("px ") + .append(border.getStyle()).append(" ").append(null == color ? "" : "rgb(" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ");"); + return sb.toString(); + } + + private boolean verify(String width, String style) { + return null != width && width.length() > 0 && null != style && style.length() > 0; + } + + private boolean verify(Border border) { + return null != border && border.getWidth() > 0 && null != border.getStyle(); + } +} diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderEnum.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderEnum.java new file mode 100644 index 000000000..034ebc1da --- /dev/null +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/BorderEnum.java @@ -0,0 +1,79 @@ +package com.fr.third.v2.lowagie.text.html; + +import java.util.List; +import java.util.Map; + +/** + * @author Hugh.C + * @version 1.0 + * Created by Hugh.C on 2020/6/15 + */ +public enum BorderEnum { + TOP { + @Override + public void fillAttr(Map map, List borderAttr) { + map.put(Markup.CSS_KEY_BORDERWIDTHTOP, borderAttr.get(0)); + map.put(Markup.CSS_KEY_BORDERSTYLETOP, borderAttr.get(1)); + map.put(Markup.CSS_KEY_BORDERCOLORTOP, borderAttr.get(2)); + } + }, + RIGHT { + @Override + public void fillAttr(Map map, List borderAttr) { + map.put(Markup.CSS_KEY_BORDERWIDTHRIGHT, borderAttr.get(0)); + map.put(Markup.CSS_KEY_BORDERSTYLERIGHT, borderAttr.get(1)); + map.put(Markup.CSS_KEY_BORDERCOLORRIGHT, borderAttr.get(2)); + } + }, + BOTTOM { + @Override + public void fillAttr(Map map, List borderAttr) { + map.put(Markup.CSS_KEY_BORDERWIDTHBOTTOM, borderAttr.get(0)); + map.put(Markup.CSS_KEY_BORDERSTYLEBOTTOM, borderAttr.get(1)); + map.put(Markup.CSS_KEY_BORDERCOLORBOTTOM, borderAttr.get(2)); + } + }, + LEFT { + public void fillAttr(Map map, List borderAttr) { + map.put(Markup.CSS_KEY_BORDERWIDTHLEFT, borderAttr.get(0)); + map.put(Markup.CSS_KEY_BORDERSTYLELEFT, borderAttr.get(1)); + map.put(Markup.CSS_KEY_BORDERCOLORLEFT, borderAttr.get(2)); + } + }, + + DEFAULT { + @Override + public void fillAttr(Map map, List borderAttr) { + } + }; + + public static BorderEnum get(String str) { + switch (str) { + case Markup.CSS_KEY_BORDERTOP: + return TOP; + case Markup.CSS_KEY_BORDERRIGHT: + return RIGHT; + case Markup.CSS_KEY_BORDERBOTTOM: + return BOTTOM; + case Markup.CSS_KEY_BORDERLEFT: + return LEFT; + default: + return DEFAULT; + } + } + + public abstract void fillAttr(Map map, List borderAttr); + + public void dealAttr(Map map, List borderAttr) { + if (null == map || null == borderAttr || borderAttr.size() > 3 || borderAttr.size() < 2) { + return; + } + //保证 borderAttr 大小为3,即 线条粗细、样式、颜色都需要具备 + if (2 == borderAttr.size()) { + borderAttr.add("black"); + } + fillAttr(map,borderAttr); + } + + +} diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java index 63e3a3a04..85d8a7553 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/Markup.java @@ -178,12 +178,40 @@ public class Markup { /** the CSS tag for the margin of an object */ public static final String CSS_KEY_PADDINGBOTTOM = "padding-bottom"; + public static final String CSS_KEY_BORDERTOP = "border-top"; + + public static final String CSS_KEY_BORDERRIGHT= "border-right"; + + public static final String CSS_KEY_BORDERBOTTOM= "border-bottom"; + + public static final String CSS_KEY_BORDERLEFT= "border-left"; + + public static final String CSS_KEY_BORDERSTYLETOP = "border-top-style"; + + public static final String CSS_KEY_BORDERSTYLERIGHT= "border-right-style"; + + public static final String CSS_KEY_BORDERSTYLEBOTTOM= "border-bottom-style"; + + public static final String CSS_KEY_BORDERSTYLELEFT= "border-left-style"; + + public static final String CSS_KEY_BORDERCOLORTOP = "border-top-color"; + + public static final String CSS_KEY_BORDERCOLORRIGHT= "border-right-color"; + + public static final String CSS_KEY_BORDERCOLORBOTTOM= "border-bottom-color"; + + public static final String CSS_KEY_BORDERCOLORLEFT= "border-left-color"; + + public static final String CSS_KEY_BORDER = "border"; + /** the CSS tag for the margin of an object */ public static final String CSS_KEY_BORDERCOLOR = "border-color"; /** the CSS tag for the margin of an object */ public static final String CSS_KEY_BORDERWIDTH = "border-width"; + public static final String CSS_KEY_BORDERSTYLE = "border-style"; + /** the CSS tag for the margin of an object */ public static final String CSS_KEY_BORDERWIDTHLEFT = "border-left-width"; @@ -533,6 +561,37 @@ public class Markup { return result.toString(); } + /** + * 解析类似于 border:1px solid #000 中的值 + * + * @param attr 如 1px solid #000 or 1px solid + * @return list: 按粗细、样式、颜色的顺序返回 ,没有则为null + */ + public static List parseBorderAttr(String attr) { + if (null == attr) { + return null; + } + List list = new ArrayList(3); + StringTokenizer st = new StringTokenizer(attr); + while (st.hasMoreElements()) { + list.add(st.nextToken()); + } + switch (list.size()) { + case 2: + // 如 border:1px solid ,则默认颜色为黑色 + list.add("black"); + break; + case 3: + // 如 border:1px solid black ,已经写全了,不用做额外的处理 + break; + default: + // 如 border:1px ,此时浏览器是无法识别的 + list = null; + break; + } + return list; + } + /** * 解析类似于 margin: 10px 5px 15px 20px,中的值 * NESW:北东南西(上右下左) diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java index e96b74d5c..852275e24 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/ParseIndentAttrUtils.java @@ -71,6 +71,22 @@ public class ParseIndentAttrUtils { return indentAttribute; } + public static IndentAttribute parseBorderAttribute(BorderAttribute borderAttr) { + IndentAttribute widthAttr = new IndentAttribute(); + if (null == borderAttr) { + return widthAttr; + } + widthAttr.setTop(verify(borderAttr.getTop()) ? borderAttr.getTop().getWidth() : 0); + widthAttr.setRight(verify(borderAttr.getRight()) ? borderAttr.getRight().getWidth() : 0); + widthAttr.setBottom(verify(borderAttr.getBottom()) ? borderAttr.getBottom().getWidth() : 0); + widthAttr.setBottom(verify(borderAttr.getLeft()) ? borderAttr.getLeft().getWidth() : 0); + return widthAttr; + } + + private static boolean verify(Border border) { + return null != border && 0 < border.getWidth() && null != border.getStyle(); + } + public static void createIndent(StringBuffer stringBuffer, String attr, IndentAttribute indentAttribute){ stringBuffer.append(attr).append("-").append("left").append(":").append(indentAttribute.getLeft()).append("px;") .append(attr).append("-").append("right").append(":").append(indentAttribute.getRight()).append("px;") diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java index 8a4122ca6..c0a7d6ae0 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/ChainedProperties.java @@ -202,6 +202,21 @@ public class ChainedProperties { return false; } + /** + * 获取位于最后的块标签 + * + * @return + */ + public String getLastBlockTag() { + for (int i = chain.size() - 1; i >= 0; --i) { + Object obj[] = (Object[]) chain.get(i); + if (HtmlConstants.BLOCK_ELEMENTS.contains(obj[0])) { + return (String) obj[0]; + } + } + return null; + } + /** * 获取当前处理的标签 * diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java index 752d6bb2d..6b17e07c6 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/FactoryProperties.java @@ -51,9 +51,13 @@ package com.fr.third.v2.lowagie.text.html.simpleparser; import com.fr.third.v2.lowagie.text.html.CSS; +import com.fr.third.v2.lowagie.text.html.BorderAttribute; +import com.fr.third.v2.lowagie.text.html.utils.BackgroundUtil; +import com.fr.third.v2.lowagie.text.html.utils.BorderUtils; import java.awt.Color; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; @@ -128,9 +132,21 @@ public class FactoryProperties { } public static void createParagraph(Paragraph p, ChainedProperties props) { - if(props.hasPropertyInChain("div", "background")){ - p.setBackground(props.getPropertyFromChain("div", "background")); + String value = props.getProperty("align"); + if (value != null) { + if (value.equalsIgnoreCase("center")) + p.setAlignment(Element.ALIGN_CENTER); + else if (value.equalsIgnoreCase("right")) + p.setAlignment(Element.ALIGN_RIGHT); + else if (value.equalsIgnoreCase("justify")) + p.setAlignment(Element.ALIGN_JUSTIFIED); } + Map bgAttrMap = BackgroundUtil.parseBgAttr4BlockTag(props); + if (null != bgAttrMap) { + for (Map.Entry entry : bgAttrMap.entrySet()) { + p.setAttribute(entry.getKey(), entry.getValue()); + } + } if(props.hasProperty("line-height")){ p.setAttribute("line-height", props.getProperty("line-height")); } @@ -142,6 +158,11 @@ public class FactoryProperties { parseMarginAttr(p, props); parsePaddingAttr(p, props); + String lastBlockTag = props.getLastBlockTag(); + //这里解析的背景属性不能是table标签的 + if (null != lastBlockTag && !HtmlTags.TABLE.equals(lastBlockTag)) { + p.setBorderAttrs(new BorderAttribute().applyBorderAttr(BorderUtils.parse2RulesMap(props, false))); + } if(props.hasPropertyInChain("div", "text-indent")){ String ss = props.getPropertyFromChain("div", "text-indent"); p.setFirstLineIndent(Markup.parseLength(ss)); @@ -153,7 +174,7 @@ public class FactoryProperties { p.setHyphenation(getHyphenation(props)); setParagraphLeading(p, props.getProperty("leading")); - String value = props.getProperty("before"); + value = props.getProperty("before"); if (value != null) { try { p.setSpacingBefore(Float.parseFloat(value)); diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java index a86cb9205..97949db6e 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/HtmlConstants.java @@ -11,6 +11,8 @@ import java.util.List; * @date 2018/5/2 */ public class HtmlConstants { + //表格相关标签集合 + public static final List TABLE_LABEL_GROUP = new ArrayList(); //块级元素集合 public static final List BLOCK_ELEMENTS = new ArrayList(); //行内元素集合 @@ -18,7 +20,17 @@ public class HtmlConstants { //支持解析的属性 public static final List PADDING = new ArrayList(); public static final List MARGIN = new ArrayList(); + //边框属性 + public static final List BORDER = new ArrayList(); + public static final List BORDER_WIDTH = new ArrayList(); + public static final List BORDER_STYLE = new ArrayList(); + public static final List BORDER_COLOR = new ArrayList(); static { + TABLE_LABEL_GROUP.add(HtmlTags.TABLE); + TABLE_LABEL_GROUP.add(HtmlTags.ROW); + TABLE_LABEL_GROUP.add(HtmlTags.CELL); + TABLE_LABEL_GROUP.add(HtmlTags.HEADERCELL); + BLOCK_ELEMENTS.add(HtmlTags.DIV); BLOCK_ELEMENTS.add(HtmlTags.UNORDEREDLIST); BLOCK_ELEMENTS.add(HtmlTags.PARAGRAPH); @@ -43,5 +55,27 @@ public class HtmlConstants { MARGIN.add(Markup.CSS_KEY_MARGINRIGHT); MARGIN.add(Markup.CSS_KEY_MARGINBOTTOM); MARGIN.add(Markup.CSS_KEY_MARGINLEFT); + + //按照上 右 下 左 的顺序排 + BORDER.add(Markup.CSS_KEY_BORDERTOP); + BORDER.add(Markup.CSS_KEY_BORDERRIGHT); + BORDER.add(Markup.CSS_KEY_BORDERBOTTOM); + BORDER.add(Markup.CSS_KEY_BORDERLEFT); + + BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHTOP); + BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHRIGHT); + BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHBOTTOM); + BORDER_WIDTH.add(Markup.CSS_KEY_BORDERWIDTHLEFT); + + BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLETOP); + BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLERIGHT); + BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLEBOTTOM); + BORDER_STYLE.add(Markup.CSS_KEY_BORDERSTYLELEFT); + + BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORTOP); + BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORRIGHT); + BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORBOTTOM); + BORDER_COLOR.add(Markup.CSS_KEY_BORDERCOLORLEFT); + } } diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncCell.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncCell.java index e3211042c..6b2af0674 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncCell.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncCell.java @@ -108,6 +108,7 @@ public class IncCell implements TextElementArray { //解析td上声明的width value = props.getLastChainProperty("width"); if (value != null) { + cell.setOriginalStyleWidth(value); cell.setStyleWidth(CSSUtils.parseFloat(value)); } //解析td上声明的height diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncTable.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncTable.java index 3fca955a7..9b580d395 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncTable.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/simpleparser/IncTable.java @@ -110,19 +110,18 @@ public class IncTable { public PdfPTable buildTable() { if (rows.isEmpty()) return new PdfPTable(1); - int ncol = 0; ArrayList c0 = (ArrayList) rows.get(0); - for (int k = 0; k < c0.size(); ++k) { - ncol += ((PdfPCell) c0.get(k)).getColspan(); - } + List firstLineColWidths = getFirstLineColWidths(); + int ncol = getMaxColCount(); processColWidth(ncol); PdfPTable table = new PdfPTable(ncol); try { TableProperties tableProperties = parseTableProperties(); table.setTableProperties(tableProperties); + table.setFirstLineColWidths(firstLineColWidths); //相对宽度 float[] floats = new float[relativeColWidths.size()]; @@ -148,19 +147,70 @@ public class IncTable { } } for (int row = 0; row < rows.size(); ++row) { - ArrayList col = (ArrayList)rows.get(row); + ArrayList col = (ArrayList) rows.get(row); + int colCount=0; for (int k = 0; k < col.size(); ++k) { - table.addCell((PdfPCell)col.get(k)); + table.addCell((PdfPCell) col.get(k)); + colCount += ((PdfPCell) col.get(k)).getColspan(); + } + //补充空白列 + for (int i = 0; i < ncol - colCount; i++) { + PdfPCell pdfPCell = new PdfPCell(); + pdfPCell.setInvalid(true); + table.addCell(pdfPCell); } } - processRowHeight(table); + table.adjustFixedLayoutColumnWidth(); }catch (Exception e){ e.printStackTrace(); } return table; } + /** + * 获取第一行的列宽 + * + * @return + */ + private List getFirstLineColWidths() { + ArrayList c0 = (ArrayList) rows.get(0); + List firstLineColWidths = new ArrayList(); + for (int i = 0; i < c0.size(); i++) { + PdfPCell pdfPCell = (PdfPCell) c0.get(i); + String width = pdfPCell.getOriginalStyleWidth(); + if (null != width && 1 < pdfPCell.getColspan()) { + float w = CSSUtils.parseFloat(width); + //平分且需要保持原始的样式(有百分号的需要加上百分百) + w /= pdfPCell.getColspan(); + width = width.endsWith("%") ? String.valueOf(w) + "%" : String.valueOf(w); + } + for (int j = 0; j < pdfPCell.getColspan(); j++) { + firstLineColWidths.add(width); + } + } + return firstLineColWidths; + } + + /** + * 获取最大列数 + * + * @return + */ + public int getMaxColCount() { + int max = 0; + for (int row = 0; row < rows.size(); ++row) { + ArrayList col = (ArrayList) rows.get(row); + int ncol = 0; + for (int k = 0; k < col.size(); ++k) { + ncol += ((PdfPCell) col.get(k)).getColspan(); + } + max = Math.max(ncol, max); + } + return max; + } + + /** * 调整列宽,每列取最大值 * @@ -177,7 +227,7 @@ public class IncTable { for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { ArrayList cols = (ArrayList) rows.get(rowIndex); int colSpan = 1; - for (int colIndex = 0; colIndex < cols.size(); colIndex+=colSpan) { + for (int colIndex = 0; colIndex < cols.size(); colIndex += colSpan) { PdfPCell pCell = ((PdfPCell) cols.get(colIndex)); colSpan = pCell.getColspan(); float avgWidth = pCell.getStyleWidth() / colSpan; @@ -290,6 +340,15 @@ public class IncTable { if(value != null){ tableProperties.setCellpadding(CSSUtils.parseFloat(value)); } + value = (String) props.get("width"); + if (value != null) { + tableProperties.setStyleWidth(value); + } + value = (String) props.get("table-layout"); + if (value != null) { + tableProperties.setLayout(value); + } + ChainedProperties properties = new ChainedProperties(); properties.addToChain(HtmlTags.TABLE, props); //解析background相关属性 diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BackgroundUtil.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BackgroundUtil.java index 23cc892df..3e95ef0c9 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BackgroundUtil.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BackgroundUtil.java @@ -45,4 +45,40 @@ public class BackgroundUtil { } return backgroundRules; } + + /** + * 将块元素的背景属性转换成mp + * + * @param props + * @return + */ + public static Map parseBgAttr4BlockTag(ChainedProperties props) { + Map backgroundRules = new HashMap(); + String value = props.getPropertyFromChain("div", "background-color"); + if (value != null) { + backgroundRules.put("background-color", value); + } + value = props.getPropertyFromChain("div", "background-size"); + if (value != null) { + backgroundRules.put("background-size", value); + } + value = props.getPropertyFromChain("div", "background"); + if (value != null) { + Map backgroundStyles = CSSUtils.processBackground(value); + backgroundRules.putAll(backgroundStyles); + } + value = props.getPropertyFromChain("div", "background-position"); + if (value != null) { + backgroundRules.put("background-position", value); + } + value = props.getPropertyFromChain("div", "background-repeat"); + if (value != null) { + backgroundRules.put("background-repeat", value); + } + value = props.getPropertyFromChain("div", "background-image"); + if (value != null) { + backgroundRules.put("background-image", value); + } + return backgroundRules; + } } diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BorderUtils.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BorderUtils.java new file mode 100644 index 000000000..62fca479e --- /dev/null +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/html/utils/BorderUtils.java @@ -0,0 +1,83 @@ +package com.fr.third.v2.lowagie.text.html.utils; + + +import com.fr.third.v2.lowagie.text.html.BorderEnum; +import com.fr.third.v2.lowagie.text.html.HtmlTags; +import com.fr.third.v2.lowagie.text.html.Markup; +import com.fr.third.v2.lowagie.text.html.simpleparser.ChainedProperties; +import com.fr.third.v2.lowagie.text.html.simpleparser.HtmlConstants; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Hugh.C + * @version 1.0 + * Created by Hugh.C on 2020/5/27 + */ +public class BorderUtils { + + /** + * 解析border属性 + * + * @param props + * @param lastTag true:解析最后一个tag 的边框属性 + */ + public static Map parse2RulesMap(ChainedProperties props, boolean lastTag) { + Map map = new HashMap(); + String value = lastTag ? props.getLastChainProperty(Markup.CSS_KEY_BORDER) : props.getPropertyFromChain(HtmlTags.DIV, Markup.CSS_KEY_BORDER); + //解析 border + if (null != value) { + List list = Markup.parseBorderAttr(value); + for (BorderEnum border : BorderEnum.values()) { + border.dealAttr(map, list); + } + } + + //解析 border-width + parseBorderAttr(Markup.CSS_KEY_BORDERWIDTH, HtmlConstants.BORDER_WIDTH, map, props, lastTag); + //解析 border-style + parseBorderAttr(Markup.CSS_KEY_BORDERSTYLE, HtmlConstants.BORDER_STYLE, map, props, lastTag); + //解析 border-color + parseBorderAttr(Markup.CSS_KEY_BORDERCOLOR, HtmlConstants.BORDER_COLOR, map, props, lastTag); + + //解析 border-top|right|bottom|left + for (String border : HtmlConstants.BORDER) { + value = lastTag ? props.getLastChainProperty(border) : props.getPropertyFromChain(HtmlTags.DIV, border); + if (null != value) { + BorderEnum.get(border).dealAttr(map, Markup.parseBorderAttr(value)); + } + } + + //解析 border-top|right|bottom|left-width + parseBorderAttr(HtmlConstants.BORDER_WIDTH, map, props, lastTag); + //解析 border-top|right|bottom|left-style + parseBorderAttr(HtmlConstants.BORDER_STYLE, map, props, lastTag); + //解析 border-top|right|bottom|left-color + parseBorderAttr(HtmlConstants.BORDER_COLOR, map, props, lastTag); + return map; + } + + private static void parseBorderAttr(List attrKeys, Map map, ChainedProperties props, boolean lastTag) { + String value = null; + for (String key : attrKeys) { + value = lastTag ? props.getLastChainProperty(key) : props.getPropertyFromChain(HtmlTags.DIV, key); + if (null != value) { + map.put(key, value); + } + } + } + + private static void parseBorderAttr(String key, List attrKeys, Map map, ChainedProperties props, boolean lastTag) { + String value = lastTag ? props.getLastChainProperty(key) : props.getPropertyFromChain(HtmlTags.DIV, key); + if (null != value) { + List list = Markup.parseNESW(value); + if (null != list) { + for (int i = 0; i < attrKeys.size() && i < list.size(); i++) { + map.put(attrKeys.get(i), list.get(i)); + } + } + } + } + +} \ No newline at end of file diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPCell.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPCell.java index 102e4e9d7..edb23a080 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPCell.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPCell.java @@ -71,6 +71,8 @@ public class PdfPCell extends Rectangle { private float styleWidth = 0.0f; private float styleHeight = 0.0f; + private String originalStyleWidth; + public float getStyleHeight() { return styleHeight; } @@ -85,6 +87,8 @@ public class PdfPCell extends Rectangle { public Map background ; + private boolean invalid = false; + public Map getBackground() { return background; } @@ -97,6 +101,21 @@ public class PdfPCell extends Rectangle { this.styleWidth = styleWidth; } + public String getOriginalStyleWidth() { + return originalStyleWidth; + } + + public void setOriginalStyleWidth(String originalStyleWidth) { + this.originalStyleWidth = originalStyleWidth; + } + + public boolean isInvalid() { + return invalid; + } + + public void setInvalid(boolean invalid) { + this.invalid = invalid; + } private ColumnText column = new ColumnText(null); @@ -291,6 +310,7 @@ public class PdfPCell extends Rectangle { background = cell.background; styleWidth = cell.styleWidth; styleHeight = cell.styleHeight; + invalid=cell.invalid; } /** diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPRow.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPRow.java index 466ee0df8..38a03958b 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPRow.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPRow.java @@ -141,7 +141,7 @@ public class PdfPRow { * @param widths * @return true if everything went right */ - public boolean setWidths(float widths[]) { + public boolean setWidths(float widths[], TableProperties tablePropertie) { if (widths.length != cells.length) return false; System.arraycopy(widths, 0, this.widths, 0, cells.length); @@ -160,6 +160,19 @@ public class PdfPRow { for (; k < last; ++k) total += widths[k]; --k; + float borderWidth=0; + float cellspacing=0; + float cellpadding=0; + if (null != tablePropertie) { + borderWidth = tablePropertie.getBorderWidth(); + cellspacing = tablePropertie.getCellspacing(); + cellpadding = tablePropertie.getCellpadding(); + } + if (cell.getColspan() > 1) { + total += ((cell.getColspan() - 1) * 2 * borderWidth); + total += ((cell.getColspan() - 1) * cellspacing); + total += ((cell.getColspan() - 1) * 2 * cellpadding); + } cell.setRight(total); cell.setTop(0); processColWidth(cell.getColumn().getCompositeElements(), cell.getWidth()); diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPTable.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPTable.java index 38d9a250f..737b16303 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPTable.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/PdfPTable.java @@ -49,6 +49,8 @@ package com.fr.third.v2.lowagie.text.pdf; +import com.fr.third.v2.lowagie.text.html.CSS; +import com.fr.third.v2.lowagie.text.html.CSSUtils; import java.util.ArrayList; import com.fr.third.v2.lowagie.text.DocumentException; @@ -59,6 +61,7 @@ import com.fr.third.v2.lowagie.text.Image; import com.fr.third.v2.lowagie.text.Phrase; import com.fr.third.v2.lowagie.text.Rectangle; import com.fr.third.v2.lowagie.text.pdf.events.PdfPTableEventForwarder; +import java.util.List; /** * This is a table that can be put at an absolute position but can also @@ -105,6 +108,8 @@ public class PdfPTable implements LargeElement { protected PdfPTableEvent tableEvent; private TableProperties tableProperties; + private List firstLineColWidths; + public TableProperties getTableProperties() { return tableProperties; } @@ -113,6 +118,10 @@ public class PdfPTable implements LargeElement { this.tableProperties = tableProperties; } + public void setFirstLineColWidths(List firstLineColWidths) { + this.firstLineColWidths = firstLineColWidths; + } + /** * Holds value of property headerRows. */ @@ -369,12 +378,244 @@ public class PdfPTable implements LargeElement { return; this.totalWidth = totalWidth; totalHeight = 0; + if (adjustFixedLayoutColumnWidth()) { + return; + } calculateWidths(); // calculateHeights(true); + adjustRowWidth(); + } + + private void adjustRowWidth() { for (int k = 0; k < rows.size(); ++k) { PdfPRow row = (PdfPRow) rows.get(k); - row.setWidths(absoluteWidths); + row.setWidths(absoluteWidths, tableProperties); + } + } + + /** + * 计算非内容区域的宽度(边框+cellpadding+cellspacing) + * + * @return + */ + private float calOutOfContentWidth() { + int numCols = getNumberOfColumns(); + float allCellspacingWidth = tableProperties.getCellspacing() * (numCols + 1); + float allCellpaddingWidth = numCols * 2 * tableProperties.getCellpadding(); + float allCellBorderWidth = (numCols + 1) * 2 * tableProperties.getBorderWidth(); + return allCellspacingWidth + allCellpaddingWidth + allCellBorderWidth; + } + + /** + * 调整固定布局(table-layout:fixed )的列宽 + * + * @return false : 不符合table-layout:fixed 的定义 + */ + public boolean adjustFixedLayoutColumnWidth() { + if (!needFixedLayout()) { + return false; + } + //是fixed 布局 且设置了width(仅设置 table-layout:fixed 而不设置width 时 前台依旧使用的是auto 布局) + + //内容区域外的宽度 + float outOfContentWidth = calOutOfContentWidth(); + //实际内容宽度 + float contentWidth = totalWidth - outOfContentWidth; + + //重新初始化一下 + absoluteWidths = new float[relativeWidths.length]; + + if (dealFirstLineHasNoWidth(contentWidth)) { + adjustRowWidth(); + return true; + } + + //第一行列宽 + float[] colWidths = new float[firstLineColWidths.size()]; + //绝对宽度和 + float absWidthSum = 0; + //相对宽度和 + float relWidthSum = 0; + //绝对宽度 (如 10px) 的索引 + ArrayList absIndex = new ArrayList(); + //相对宽度 (如 10%) 的索引 + ArrayList relIndex = new ArrayList(); + + //初始化一下上述字段 + for (int i = 0; i < firstLineColWidths.size(); i++) { + String widthStr = firstLineColWidths.get(i); + float w = CSSUtils.parseFloat(widthStr); + if (null != widthStr) { + if (widthStr.endsWith("%")) { + w = w / 100 * contentWidth; + relWidthSum += w; + relIndex.add(i); + } else { + absWidthSum += w; + absIndex.add(i); + } + } + colWidths[i] = w; + } + + //第一行td中指定的绝对宽度 >= table标签上指定的宽度,以td的宽度为准,将table撑开 + if (absWidthSum >= contentWidth) { + for (int i = 0; i < absIndex.size(); i++) { + absoluteWidths[absIndex.get(i)] = colWidths[absIndex.get(i)]; + } + totalWidth = outOfContentWidth + absWidthSum; + dealInvalidCell(absIndex); + adjustRowWidth(); + return true; + } + + //table 总的列数 + int colNum = getNumberOfColumns(); + //相对宽度与绝对宽度的列数和 + int absAndRelColNum = absIndex.size() + relIndex.size(); + //相对宽度与绝对宽度的列宽和 + float absAndRelColWidthSum = absWidthSum + relWidthSum; + + //第一行td中指定的绝对宽度+相对宽度小于table内容宽度 + if (absAndRelColWidthSum < contentWidth) { + //剩余宽度 + float remaindWidth = contentWidth - absAndRelColWidthSum; + //如果总列数大于第一行绝对宽度和相对宽度列数,则多余的宽度由剩余列平分 + if (colNum > absAndRelColNum) { + fillAbsoluteWidths(colWidths, absIndex); + fillAbsoluteWidths(colWidths, relIndex); + float remaindAvgWidth = remaindWidth / (colNum - absAndRelColNum); + for (int i = 0; i < absoluteWidths.length; i++) { + if (absIndex.contains(i) || relIndex.contains(i)) { + continue; + } + absoluteWidths[i] = remaindAvgWidth; + } + } else { + //按比例分摊剩余宽度 + for (int i = 0; i < absIndex.size(); i++) { + float width = colWidths[absIndex.get(i)]; + absoluteWidths[absIndex.get(i)] = width + remaindWidth * width / absAndRelColWidthSum; + } + for (int i = 0; i < relIndex.size(); i++) { + float width = colWidths[relIndex.get(i)]; + absoluteWidths[relIndex.get(i)] = width + remaindWidth * width / absAndRelColWidthSum; + } + } + + } else { + //第一行td中指定的绝对宽度+相对宽度大于table标签上指定的宽度,剩余宽度由相对宽度平分 + fillAbsoluteWidths(colWidths, absIndex); + //剩余宽度 + float remaindWidth = contentWidth - absWidthSum; + for (int i = 0; i < relIndex.size(); i++) { + float width = colWidths[relIndex.get(i)]; + absoluteWidths[relIndex.get(i)] = remaindWidth * width / relWidthSum; + } + dealInvalidCell(absIndex, relIndex); } + adjustRowWidth(); + return true; + } + + private boolean needFixedLayout() { + return totalWidth > 0 && rows.size() > 0 && CSS.Value.FIXED.equals(tableProperties.getLayout()) && null != tableProperties.getStyleWidth(); + } + + /** + * 获取列索引List + */ + public List getColIndexList() { + List list = new ArrayList<>(absoluteWidths.length); + for (int i = 0; i < absoluteWidths.length; i++) { + list.add(i); + } + return list; + } + + /** + * 填充最终宽度 + * + * @param colWidths 列宽 + * @param indexList 要设置的列索引List + */ + private void fillAbsoluteWidths(float[] colWidths, ArrayList indexList) { + for (int i = 0; i < indexList.size() && indexList.get(i) < absoluteWidths.length; i++) { + Integer index = indexList.get(i); + absoluteWidths[index] = colWidths[index]; + } + } + + /** + * validIndexList 之外的列对应的单元格设置为无效的 + * + * @param validIndexList + */ + private void dealInvalidCell(List... validIndexList) { + if (null == validIndexList) { + return; + } + //计算无效列索引 + List invalidIndexList = getColIndexList(); + + List list = new ArrayList(); + for (List item : validIndexList) { + list.addAll(item); + } + invalidIndexList.removeAll(list); + for (int row = 0; row < rows.size(); row++) { + PdfPRow pdfRow = (PdfPRow) rows.get(row); + int index = 0; + PdfPCell[] cells = pdfRow.getCells(); + for (PdfPCell cell : cells) { + if (cell == null) { + index += 1; + continue; + } + if (invalidIndexList.contains(index)) { + cell.setInvalid(true); + } + index += cell.getColspan(); + } + } + //将无效列的多余宽度平分给其他列 + dealInvalidColWidth(invalidIndexList.size(), list); + } + + /** + * 处理无效列的内容区域外的宽度(平分给其他列) + * + * @param invalidColNum + * @param validIndexList + */ + private void dealInvalidColWidth(int invalidColNum, List validIndexList) { + if (0 == invalidColNum || null == validIndexList || 0 == validIndexList.size()) { + return; + } + float invalidColWidth = invalidColNum * tableProperties.getCellspacing() + 2 * invalidColNum * (tableProperties.getCellpadding() + tableProperties.getBorderWidth()); + float avgWidth = invalidColWidth / validIndexList.size(); + for (int i = 0; i < validIndexList.size() && validIndexList.get(i) < absoluteWidths.length; i++) { + Integer index = validIndexList.get(i); + absoluteWidths[index] += avgWidth; + } + } + + /** + * 处理第一行没有设置width属性情况 + * + * @param contentWidth 内容宽度 + * @return + */ + private boolean dealFirstLineHasNoWidth(float contentWidth) { + if (null != firstLineColWidths && 0 != firstLineColWidths.size()) { + return false; + } + //第一行的td都没有设置width属性,则所有列平分table宽度 + int length = absoluteWidths.length; + for (int i = 0; i < length; i++) { + absoluteWidths[i] = contentWidth / length; + } + return true; } /** @@ -509,7 +750,7 @@ public class PdfPTable implements LargeElement { } PdfPRow row = new PdfPRow(currentRow); if (totalWidth > 0) { - row.setWidths(absoluteWidths); + row.setWidths(absoluteWidths, tableProperties); // totalHeight += row.getMaxHeights(); } rows.add(row); @@ -876,7 +1117,7 @@ public class PdfPTable implements LargeElement { if (row == null) return 0; if (firsttime) - row.setWidths(absoluteWidths); + row.setWidths(absoluteWidths, tableProperties); float height = row.getMaxHeights(); PdfPCell cell; PdfPRow tmprow; diff --git a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/TableProperties.java b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/TableProperties.java index a886f136e..6e46128f7 100644 --- a/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/TableProperties.java +++ b/fine-itext/src/main/java/com/fr/third/v2/lowagie/text/pdf/TableProperties.java @@ -11,7 +11,11 @@ public class TableProperties { private float cellspacing = 2.0f; private float cellpadding = 1.0f; private boolean collapse = false; + public Map background; + private String styleWidth = null; + + private String layout = null; public BorderStyle getBorderStyle() { return borderStyle; @@ -57,6 +61,22 @@ public class TableProperties { this.background = background; } + public String getStyleWidth() { + return styleWidth; + } + + public void setStyleWidth(String styleWidth) { + this.styleWidth = styleWidth; + } + + public String getLayout() { + return layout; + } + + public void setLayout(String layout) { + this.layout = layout; + } + public String toHtmlString(){ StringBuffer sb = new StringBuffer(); if(!isCollapse()){