All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ofdrw.layout.element.Paragraph Maven / Gradle / Ivy

The newest version!
package org.ofdrw.layout.element;

import org.ofdrw.font.Font;
import org.ofdrw.layout.Rectangle;

import java.util.LinkedList;
import java.util.List;

/**
 * 段落
 * 

* 绘制行为详见渲染器:{@link org.ofdrw.layout.engine.render.ParagraphRender} * * @author 权观宇 * @since 2020-02-03 01:27:20 */ public class Paragraph extends Div { /** * 首行缩进字符数 *

* null 标识没有缩进 */ private Integer firstLineIndent = null; /** * 首行缩进数值 * 单位:mm */ private Double firstLineIndentWidth = null; /** * 行间距 */ private Double lineSpace = 2d; /** * 默认字体 */ private Font defaultFont; /** * 默认字号 */ private Double defaultFontSize; /** * 文字内容 */ private LinkedList contents; /** * 元素内行缓存 */ private LinkedList lines; /** * 字体在段落内的浮动方向 *

* 默认为:左浮动 */ private TextAlign textAlign = TextAlign.left; /** * 创建一个固定大小段落对象 * * @param width 内容宽度 * @param height 内容高度 */ public Paragraph(Double width, Double height) { this(); setWidth(width).setHeight(height); } /** * 创建一个段落对象 *

* 注意如果不主动对 Paragraph 设置宽度,那么Paragraph * 会独占整个段,并且与段具有相同宽度,也就是页面宽度, * 在需要更加细致盒式的布局时 请设置{@link #setClear(Clear)} * 并设置段落宽度 {@link #setWidth(Double)}。 */ public Paragraph() { this.setClear(Clear.both); this.contents = new LinkedList<>(); this.lines = new LinkedList<>(); } /** * 创建绝对定位段落对象 * * @param x 固定布局的盒式模型左上角X坐标 * @param y 固定布局的盒式模型左上角y坐标 * @param width 宽度 * @param height 高度 */ public Paragraph(double x, double y, double width, double height) { super(x, y, width, height); this.contents = new LinkedList<>(); this.lines = new LinkedList<>(); } /** * 创建绝对定位段落对象 * * @param x 固定布局的盒式模型左上角X坐标 * @param y 固定布局的盒式模型左上角y坐标 * @param width 宽度 * @param height 高度 * @param text 文本内容 * @param fontSize 字号 */ public Paragraph(double x, double y, double width, double height, String text, double fontSize) { super(x, y, width, height); if (text == null) { throw new IllegalArgumentException("文字内容为null"); } this.contents = new LinkedList<>(); this.lines = new LinkedList<>(); this.setFontSize(fontSize); this.add(text); } /** * 新建一个段落对象 *

* 如果在指定段落中文字大小建议使用{@link Paragraph#Paragraph(java.lang.String, java.lang.Double)} * * @param text 文字内容 */ public Paragraph(String text) { this(); if (text == null) { throw new IllegalArgumentException("文字内容为null"); } this.add(text); } /** * 新建一个段落对象,并指定文字大小 * * @param text 文字内容 * @param defaultFontSize 默认字体大小。 */ public Paragraph(String text, Double defaultFontSize) { this(); if (text == null) { throw new IllegalArgumentException("文字内容为null"); } this.setFontSize(defaultFontSize); this.add(text); } /** * 新建一个段落对象,并指定文字大小和字体 * * @param text 文字内容 * @param defaultFontSize 默认字体大小 * @param defaultFont 默认字体 */ public Paragraph(String text, Double defaultFontSize, Font defaultFont) { this(); if (text == null) { throw new IllegalArgumentException("文字内容为null"); } this.setFontSize(defaultFontSize); this.setDefaultFont(defaultFont); this.add(text); } /** * 新建一个段落对象,并指定字体 * * @param text 文字内容 * @param defaultFont 默认字体 */ public Paragraph(String text, Font defaultFont) { this(); if (text == null) { throw new IllegalArgumentException("文字内容为null"); } this.setDefaultFont(defaultFont); this.add(text); } /** * 增加段落中的文字 *

* 文字样式使用span默认字体样式 * * @param text 文本内容 * @return this */ public Paragraph add(String text) { if (text == null) { return this; } Span newTxt = new Span(text); if (this.defaultFont != null) { newTxt.setFont(defaultFont); } if (this.defaultFontSize != null) { newTxt.setFontSize(defaultFontSize); } return this.add(newTxt); } /** * 加入带有特殊样式文字内容 * * @param content 特殊样式文字内容 * @return this */ public Paragraph add(Span content) { if (content == null) { return this; } this.contents.add(content); return this; } public Double getLineSpace() { return lineSpace; } public Paragraph setLineSpace(Double lineSpace) { this.lineSpace = lineSpace; return this; } public Font getDefaultFont() { return defaultFont; } public Paragraph setDefaultFont(Font defaultFont) { this.defaultFont = defaultFont; return this; } /** * 设置默认字体 *

* 注意:在设置 defaultFont 之前被添加的内容,不会在调用 defaultFont 方法后而改变,除非指定 refreshBeforeAdd=true * * @param defaultFont 默认字体 * @param refreshBeforeAdd 是否对之前add的text内容应用这个字体 * @return this */ public Paragraph setDefaultFont(Font defaultFont, boolean refreshBeforeAdd) { this.defaultFont = defaultFont; if (refreshBeforeAdd) this.contents.forEach(span -> span.setFont(defaultFont)); return this; } public Double getFontSize() { return defaultFontSize; } /** * 设置段落内默认的字体大小 *

* 如果加入的文字没有设置大小,那么默认使用该值。 *

* 注意:该操作不会对段落内已经存在的文字生效, * 因此在添加文字之后,在调用该方法,原有的文字大小不会变换。 * * @param defaultFontSize 默认字体大小 * @return this */ public Paragraph setFontSize(Double defaultFontSize) { this.defaultFontSize = defaultFontSize; return this; } /** * 设置段落内默认的字体大小 *

* 如果加入的文字没有设置大小,那么默认使用该值。 *

* 注意:该操作不会对段落内已经存在的文字生效, * 因此在添加文字之后,在调用该方法,原有的文字大小不会变换。 * 你可以指定 refreshBeforeAdd=true 来使之前添加的内容也生效。 * * @param defaultFontSize 默认字体大小 * @param refreshBeforeAdd 是否刷新之前添加的内容 * @return this */ public Paragraph setFontSize(Double defaultFontSize, boolean refreshBeforeAdd) { this.defaultFontSize = defaultFontSize; if (refreshBeforeAdd) this.contents.forEach(span -> span.setFontSize(defaultFontSize)); return this; } public LinkedList getContents() { return contents; } public Paragraph setContents(List contents) { this.contents.clear(); this.contents.addAll(contents); return this; } public LinkedList getLines() { return lines; } /** * 创建新的行 * * @param width 行宽度 * @return 行块 */ private TxtLineBlock newLine(double width) { return new TxtLineBlock(width, lineSpace, textAlign); } /** * 获取首行缩进字符数 * * @return 首行缩进字符数 null表示没有缩进 */ public Integer getFirstLineIndent() { return firstLineIndent; } /** * 设置段落首行缩进 *

* 默认不缩进 * * @param firstLineIndent 缩进字符数null或0表示不缩进 * @return this */ public Paragraph setFirstLineIndent(Integer firstLineIndent) { this.firstLineIndent = firstLineIndent; return this; } /** * 获取首行缩进数值 *

* 该方法仅在已经设置了首行缩进数值的情况下才会返还正确的数值,否则返还null * * @return 首行缩进数值或null */ public Double getFirstLineIndentWidth() { return firstLineIndentWidth; } /** * 设置段落首行缩进数值 * * @param firstLineIndentWidth 首行缩进数值(单位:mm) * @return this */ public Paragraph setFirstLineIndentWidth(double firstLineIndentWidth) { this.firstLineIndentWidth = firstLineIndentWidth; return this; } /** * 清除缩进格式 * * @return this */ public Paragraph clearFirstLineIndent() { this.firstLineIndent = null; return this; } /** * 设置段落内字体浮动 *

* 默认为左浮动 * * @param textAlign 浮动方向 * @return this */ public Paragraph setTextAlign(TextAlign textAlign) { if (textAlign == null) { textAlign = TextAlign.left; } this.textAlign = textAlign; return this; } /** * 获取段落内字体浮动 *

* 默认为左浮动 * * @return 浮动方向 */ public TextAlign getTextAlign() { return this.textAlign; } /** * 处理Span内部含有换行符转换为占满剩余行空间的多个元素队列 * * @param seq 待处理Span队列 * @return 处理后含有占满剩余行空间的Span队列 */ private LinkedList spanLinebreakSplit(LinkedList seq) { LinkedList sps = new LinkedList<>(); if (seq == null || seq.isEmpty()) { return sps; } while (!seq.isEmpty()) { Span pop = seq.pop(); // 通过换行符对span内容进行分割,获取新的队列 LinkedList spans = pop.splitLineBreak(); sps.addAll(spans); } return sps; } /** * 处理占位符 * * @param seq 段落中的span队列 */ private void processPlaceholder(LinkedList seq) { if (seq == null || seq.isEmpty()) { return; } // 不需要加入占位符 if ((firstLineIndent == null || firstLineIndent == 0) && (firstLineIndentWidth == null || firstLineIndentWidth == 0)) { return; } Span firstSpan = seq.peek(); if (firstSpan instanceof PlaceholderSpan) { PlaceholderSpan h = (PlaceholderSpan) firstSpan; // 已经存在段落缩进并且设置的段落缩进为0,那么删除该占位符 if (h.getHoldWidth() == 0 && h.getHoldNum() == 0) { seq.pop(); } if (firstLineIndentWidth != null && firstLineIndentWidth > 0) { h.setHoldWidth(firstLineIndentWidth); } else { // 重设占位符的宽度 h.setHoldChars(firstLineIndent); } return; } if (firstLineIndentWidth != null) { // 使用指定宽度和高度的占位符 seq.push(new PlaceholderSpan(firstLineIndentWidth, firstSpan.getFontSize())); } else { // 如果第一个不是占位符,并且占位符数目大于0 那么创建新的占位符,并且加入渲染队列 seq.push(new PlaceholderSpan(firstLineIndent, firstSpan)); } } /** * 如果元素高度不存在那么设置高度 *

* 如果已经设置了高度,该方法不会对该高度造成影响 * * @param height 高度 */ private void setHeightIfNotExist(double height) { if (getHeight() == null || getHeight() == 0) { setHeight(height); } } /** * 如果宽度高度不存在那么设置宽度 *

* 如果已经设置了宽度,该方法不会对该宽度造成影响 * * @param width 宽度 */ private void setWidthIfNotExist(double width) { if (getWidth() == null || getWidth() == 0) { setWidth(width); } } /** * 预布局 *

* 该方法主要有渲染器调用,请勿主动调用该方法,除非你知道你在做什么。 * * @param widthLimit 宽度限制 * @return 元素尺寸 */ @Override public Rectangle doPrepare(Double widthLimit) { this.lines.clear(); Double originW = this.getWidth(); // 行内最大可用宽度 Double lineMaxAvailableWidth = this.getWidth(); if (widthLimit == null) { throw new NullPointerException("widthLimit为空"); } widthLimit -= widthPlus(); if (lineMaxAvailableWidth == null || (lineMaxAvailableWidth > widthLimit)) { // TODO 尺寸重设警告日志 lineMaxAvailableWidth = widthLimit; } // setWidth(width); TxtLineBlock line = newLine(lineMaxAvailableWidth); LinkedList seq = new LinkedList<>(contents); // 处理相对列中插入或调整首行缩进占位符 processPlaceholder(seq); // 处理Span中含有换行符的情况 seq = spanLinebreakSplit(seq); while (!seq.isEmpty()) { Span s = seq.pop(); // 获取Span整体的块的大小 double blockWidth = s.blockSize().getWidth(); if (blockWidth > lineMaxAvailableWidth && s.isIntegrity()) { // TODO 警告 不可分割元素如果大于行宽度则忽略 continue; } // 特殊的如果文字可以被分割,但是第一个 // 文字的大小就已经超过可用最大空间限制 // 那么丢弃系列文字 if (!s.isIntegrity() && lineMaxAvailableWidth < s.glyphList().get(0).getW()) { continue; } // 尝试向行中加入元素 boolean added = line.tryAdd(s); if (added) { // 如果加入的Span是一个需要占满剩余行空间的元素,那么新起一行 if (s.hasLinebreak()) { lines.add(line); line = newLine(lineMaxAvailableWidth); } // 成功加入 continue; } // 无法加入行内,且Span 不可分割,那么需要换行 if (s.isIntegrity()) { lines.add(line); line = newLine(lineMaxAvailableWidth); // 重新进入队列 seq.push(s); continue; } // 由于文字单元可以分割,那么尝试通过分割的方式填充该行 Span toNextLineSpan = line.trySplitAdd(s); if (toNextLineSpan == null) { // 行空间耗尽,重新加入队列 seq.push(s); } else { // 将切分后的文字单元加入队列 seq.push(toNextLineSpan); } lines.add(line); line = newLine(lineMaxAvailableWidth); } // 最后一行处理 if (!line.isEmpty()) { lines.add(line); } // 合并所有行的高度 double height = 0; for (TxtLineBlock txtLineBlock : lines) { height += txtLineBlock.getHeight(); } // 为了防止Double 类型精度导致部分情况不准确,导致元素高度不够容纳文字 // 添加 0.0001 以弥补精度损失 height += 0.0001; // 设置元素高度,如果元素已经预设高度那么则不设置 setHeightIfNotExist(height); // - 宽度 = null: 最长行宽度 // - 宽度 != null: 区间 [宽度, widthLimit] if (originW == null) { double maxWidth = lines.stream().mapToDouble(TxtLineBlock::getWidth).max().getAsDouble(); setWidth(maxWidth); } else { setWidth(Math.min(getWidth(), widthLimit)); } return new Rectangle( getWidth() + widthPlus(), getHeight() + heightPlus()); } /** * 设置行元素,并行中文字单元加入到内容中 * * @param lines 行序列 * @return this */ private Paragraph setLines(LinkedList lines) { this.lines = lines; for (TxtLineBlock item : lines) { this.contents.addAll(item.getInlineSpans()); } return this; } @Override public Div[] contentSplitAdjust(double sHeight, T div1, T div2) { // 文字内容或是Bottom 耗尽了空间 分段的情况 LinkedList seq2 = new LinkedList<>(this.lines); LinkedList seq1 = new LinkedList<>(); // 可用空间高度 double availableHeight = sHeight - getMarginTop() - getBorderTop() - getPaddingTop(); // 在加入行后,剩余可用空间高度,依次递减直至放不下一行。 double remainHeight = availableHeight; while (!seq2.isEmpty()) { TxtLineBlock line = seq2.pop(); if (remainHeight < line.getHeight()) { // 空间不足 seq2.push(line); break; } else { seq1.add(line); remainHeight -= line.getHeight(); } } Paragraph p1 = (Paragraph) div1; Paragraph p2 = (Paragraph) div2; // seq2 为空可能由于Margin等参数导致的空间不足 if (seq2.isEmpty()) { p1.setLines(seq1) .setMarginBottom(0d) .setBorderBottom(0d) .setPaddingBottom(0d); p2.setHeight(0d) .setMarginTop(0d) .setBorderTop(0d) .setPaddingTop(0d); return new Div[]{p1, p2}; } // 剩余空间一行都无法放下的情况,整个对象放到下一个段中,并使用占位符占有上一个段的空间 if (seq1.isEmpty()) { Div placeholder = Div.placeholder(this.getWidth() + widthPlus(), sHeight, this.getFloat()); return new Div[]{placeholder, this}; } // 正常情况下的分割 p1.setLines(seq1) .setMarginBottom(0d) .setBorderBottom(0d) .setPaddingBottom(0d) .setHeight(availableHeight); p2.setLines(seq2) .setMarginTop(0d) .setBorderTop(0d) .setPaddingTop(0d) .setHeight(seq2.stream().mapToDouble(TxtLineBlock::getHeight).sum()); return new Div[]{p1, p2}; } /** * 请勿调用该方法克隆段落,除非你知道你在干什么 * * @return 属性一模一样,但是没有任何内容的段落 */ @Override public Paragraph clone() { Paragraph p = new Paragraph(); p = this.copyTo(p); p.lineSpace = lineSpace; p.defaultFont = defaultFont; p.defaultFontSize = defaultFontSize; p.lines = new LinkedList<>(); p.contents = new LinkedList<>(); return p; } /** * 元素类型 *

* 关联渲染器:{@link org.ofdrw.layout.engine.render.ParagraphRender} * * @return Paragraph */ @Override public String elementType() { return "Paragraph"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy