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

org.ofdrw.layout.engine.StreamingLayoutAnalyzer Maven / Gradle / Ivy

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

import org.ofdrw.layout.PageLayout;
import org.ofdrw.layout.Rectangle;
import org.ofdrw.layout.VirtualPage;
import org.ofdrw.layout.element.AFloat;
import org.ofdrw.layout.element.Div;
import org.ofdrw.layout.element.Position;

import java.util.*;

/**
 * 流式布局分析器
 *
 * @author 权观宇
 * @since 2020-03-07 12:08:41
 */
public class StreamingLayoutAnalyzer {

    /**
     * 页面的固定可用的工作区域
     */
    private Rectangle pageWorkArea;

    /**
     * 虚拟页面的尺寸
     */
    private PageLayout layout;

    /**
     * 布局最终产生的虚拟页面序列
     */
    private LinkedList vPageList;

    /**
     * 当前剩余工作区域
     */
    private Rectangle remainArea;

    /**
     * 当前布局中的虚拟页面
     */
    private VirtualPage vPage;

    public StreamingLayoutAnalyzer(PageLayout layout) {
        pageWorkArea = layout.getWorkerArea();
        this.layout = layout;
        vPageList = new LinkedList<>();
    }

    /**
     * 进行段的布局分析
     *
     * @param segmentSequence 段序列
     * @return 虚拟页面序列
     */
    public List analyze(List segmentSequence) {
        if (segmentSequence == null || segmentSequence.isEmpty()) {
            return Collections.emptyList();
        }
        LinkedList seq = new LinkedList<>(segmentSequence);
        if (vPageList.isEmpty()) {
            // 初始化页面
            addNewPage();
        }
        while (!seq.isEmpty()) {
            Segment segment = seq.pop();
            // 页面没有剩余空间,新建页面
            if (remainArea.getHeight() == 0) {
                // 空间不足则换页
                addNewPage();
            }
            // 特殊的如果是一个填充段那么设置页面
            if (segment.isRemainAreaFiller()) {
                remainArea.setHeight(0d);
                continue;
            }
            // 高度足够能够放入剩余空间中
            if (segment.getHeight() <= remainArea.getHeight()) {
                // 分配段空间
                Rectangle area = remainArea.reduce(segment.getHeight());
                // 定位元素,加入虚拟页面
                elementPositioning(segment, area);
                continue;
            }
            // 段不可以拆分情况下
            if (segment.isBlockable() == false) {
                if (segment.getHeight() > pageWorkArea.getHeight()) {
                    // TODO 警告: 如果段不可拆分,并且高度大于整个页面的高度,那么这样的段应该舍弃
                } else {
                    // 如果段不可拆分,并且高度小于整个页面的高度,那么新起一个页面,重新加入队列布局
                    addNewPage();
                    seq.push(segment);
                }
                continue;
            }
            // 段可以拆分,那么通过剩余空间对段分块,这个分块只会分为两块
            Segment[] blocks = segmentBlocking(segment, remainArea.clone());
            if (blocks != null && blocks.length > 0) {
                // 因为是栈的原因,需要把最后一个元素最新压到栈顶。
                // blocks 逆序加入 seq
                for (int i = blocks.length - 1; i >= 0; i--) {
                    // 逆序重新进入队列中
                    seq.push(blocks[i]);
                }
            }
        }
        return vPageList;
    }

    /**
     * 由于剩余空间不足且段可以分块
     * 

* 对段进行分块,剩余的块将流转到下一个页面的段中 * * @param segment 段 * @param area 剩余空间 * @return 分块后的段序列,含两个Segment元素。 */ private Segment[] segmentBlocking(Segment segment, Rectangle area) { // 分为两块 Segment sgm1 = new Segment(segment.getWidth()); Segment sgmNext = new Segment(segment.getWidth()); for (Map.Entry item : segment) { Div div = item.getKey(); Rectangle rec = item.getValue(); // 1. 能够放置到剩余空间中 double availableHeight = area.getHeight(); if (rec.getHeight() <= availableHeight) { // 向第一个段,加入实际的Div sgm1.tryAdd(div); // 向第下一个段加入占位符 sgmNext.tryAdd(Div.placeholder(rec, div.getFloat())); continue; } // 2. 在剩余空间中无法放置,且元素本身不可分割 if (div.isIntegrity()) { // 向第一个段中加入占位符 sgm1.tryAdd(Div.placeholder(rec.getWidth(), availableHeight, div.getFloat())); // 把实际元素加入下一个段中 sgmNext.tryAdd(div); continue; } // 3. 无法在剩余空间中放置,且可以分割的情况,需要对元素进行分割 Div[] split = div.split(availableHeight); sgm1.tryAdd(split[0]); sgmNext.tryAdd(split[1]); } return new Segment[]{sgm1, sgmNext}; } /** * 段中的元素定位,并加入到虚拟页面中 * * @param segment 待定位的段 * @param area 分配给该段的页面空间 */ private void elementPositioning(Segment segment, Rectangle area) { /* 根据浮动方式,判断元素在段中X坐标定位 */ // 居中的布局分析 if (segment.isCenterFloat()) { // 获取段内所有元素的宽度 double totalWidth = segment.getSizeList().stream().mapToDouble(Rectangle::getWidth).sum(); // 第一个元素偏移的X坐标 double offsetX = area.getX() + ((area.getWidth() - totalWidth) / 2); for (Map.Entry item : segment) { Div itemDiv = item.getKey(); Rectangle box = item.getValue(); itemDiv.setX(offsetX); // 增加偏移量计算出下一个box应该出现的位置 offsetX += box.getWidth(); } } else { // 左右浮动的分析 double startX = area.getX(); double endX = startX + area.getWidth(); for (Map.Entry item : segment) { Div itemDiv = item.getKey(); Rectangle box = item.getValue(); AFloat aFloat = itemDiv.getFloat(); if (aFloat == AFloat.left) { itemDiv.setX(startX); startX += box.getWidth(); } else if (aFloat == AFloat.right) { itemDiv.setX(endX - box.getWidth()); endX -= box.getWidth(); }// ignore center and null } } for (Map.Entry item : segment) { Div itemDiv = item.getKey(); if (itemDiv.isPlaceholder()) { // 占位符不参与绘制,跳过 continue; } /* 将流式的自动定位转为绝对定位位置的Div */ itemDiv.setY(area.getY()); // 解决相对定位的位置 if (itemDiv.getPosition() == Position.Relative) { Double x = itemDiv.getX(); Double y = itemDiv.getY(); itemDiv.setX(x + itemDiv.getLeft() - itemDiv.getRight()) .setY(y + itemDiv.getTop()); } itemDiv.setPosition(Position.Absolute); // 加入到虚拟页面中 vPage.addUnsafe(itemDiv); } } /** * 创建新页面 */ private void addNewPage() { // 重置工作区域 remainArea = this.pageWorkArea.clone(); vPage = new VirtualPage(layout); vPageList.add(vPage); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy