Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

一、特殊名词介绍

二、背景、场景介绍

数据源是相对于数据集而言的。主要是在功能的设计上的一个划分。就技术实现本身而言,数据集接口其实本身是可以覆盖到几乎所有场景的。数据源的代码抽象上实质就是把一组具有部分相同配置的数据集的配置整合在一起作为一个统一的配置供数据集引用。而这些相同配置的生效过程也就是数据源的“连接”、“验证”过程。比如:我们可以写一个程序数据集,里面自己实现JDBC连接池调用JDBC的所有配置连接数据库执行SQL,从实现上来说是完全可以的。但我们把JDBC连接池和JDBC的连接配置统一抽取出来做成一个数据源配置,对于用户的使用和维护来说会更方便(如果分开每个数据集都要单独配置JDBC链接,那一旦链接信息便跟,这个维护量是相当大的)。10.0版本之前,帆软报表提供了固定报表背景设置,诸如颜色,材质,图片之类的。但对于一些“类水印”动态背景的需要实现却没有提供解决方案(10.0中已支持水印背景设置),因此从8.0版本开始,提供了背景的扩展接口。允许开发者按照业务需要去扩展相应的背景类型。其主要场景是,基于公式或条件动态的生成相应背景的场景,常见的就是“水印场景”。也常被运用于“业务主题背景”快速配置场景。

三、接口介绍

Code Block
languagejava
themeEclipse
firstline1
titleBackgroundQuickUIProvider.java
linenumberstrue
package com.fr.design.fun;

import com.fr.design.mainframe.backgroundpane.BackgroundQuickPane;
import com.fr.stable.fun.Level;
import com.fr.stable.fun.Provider;
import com.fr.stable.fun.mark.Mutable;

/**
 * Created by richie on 16/5/18.
 * 背景设置界面接口,用于扩展设置更多类型的背景
 */
public interface BackgroundQuickUIProvider extends Mutable {

    String MARK_STRING = "BackgroundQuickUIProvider";

    int CURRENT_LEVEL = 1;

    /**
     * 背景设置界面
     * @return 设置界面
     */
    BackgroundQuickPane appearanceForBackground();
}

...

Code Block
languagejava
themeEclipse
firstline1
titleImageBackground.java
linenumberstrue
collapsetrue
/*
 * Copyright(c) 2001-2010, FineReport  Inc, All Rights Reserved.
 */
package com.fr.base.background;

import com.fr.base.Base64;
import com.fr.base.BaseUtils;
import com.fr.base.GraphHelper;
import com.fr.base.ImageProvider;
import com.fr.general.Background;
import com.fr.general.ImageWithSuffix;
import com.fr.general.xml.FineImage;
import com.fr.general.xml.GeneralXMLTools;
import com.fr.json.JSONException;
import com.fr.json.JSONObject;
import com.fr.plugin.injectable.PluginModule;
import com.fr.stable.BaseConstants;
import com.fr.stable.Constants;
import com.fr.stable.StableUtils;
import com.fr.stable.StringUtils;
import com.fr.stable.bridge.StableFactory;
import com.fr.stable.fun.ImageLayoutDescriptionProcessor;
import com.fr.stable.plugin.ExtraClassManagerProvider;
import com.fr.stable.web.Repository;
import com.fr.stable.xml.XMLConstants;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLReadable;
import com.fr.stable.xml.XMLableReader;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.PrinterGraphics;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import static com.fr.general.xml.GeneralXMLTools.DEFAULT_IMAGE_FORMAT;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;

/**
 * 图片背景,该背景使用一张图片作为填充。
 */
public class ImageBackground extends AbstractBackground {
    private transient ImageWithSuffix image = null;
    private int layout = Constants.IMAGE_CENTER; //layout.
    //这个layout只是用来展示的,不保存进xml,因为图片远大于单元格时,会变成空白,需要动态改变layout以展示图片
    private int layout4Draw = Constants.IMAGE_CENTER;

    private static final int PAINT_WIDTH_OFFSET = 40;
    private static final int PAINT_HEIGHT_OFFSET = 40;

    //背景图片所在单元格坐标点
    private Point cellPoint = new Point(-1, -1);

    /**
     * 默认的构造函数
     */
    public ImageBackground() {
        this(null);
    }

    /**
     * 根据指定图片生成的图片背景
     *
     * @param image 给定的图片
     * @deprecated
     */
    public ImageBackground(Image image) {
        this(image, Constants.IMAGE_TILED);
    }

    /**
     * 根据指定的图片和布局方式生成的图片背景
     *
     * @param image  给定的图片
     * @param layout 布局方式
     * @deprecated
     */
    public ImageBackground(Image image, int layout) {
        this.setImage(ImageWithSuffix.build(image));
        this.setLayout(layout);
    }

    public ImageBackground(Image image, int layout, String path) {
        ImageWithSuffix imageWithSuffix = ImageWithSuffix.build(new FineImage(image, path));
        this.setImage(imageWithSuffix);
        this.setLayout(layout);
    }

    /**
     * 根据指定图片生成的图片背景
     *
     * @param image 给定带后缀的图片
     */
    public ImageBackground(ImageWithSuffix image) {
        this(image, Constants.IMAGE_TILED);
    }

    /**
     * 根据指定的图片和布局方式生成的图片背景
     *
     * @param image  给定带后缀的图片
     * @param layout 布局方式
     */
    public ImageBackground(ImageWithSuffix image, int layout) {
        this.image = image;
        this.layout = layout;
    }

    /**
     * 获取用于填充背景的图片
     *
     * @return 图片
     */
    public Image getImage() {
        if (this.image == null) {
            return null;
        }

        return this.image.getImage();
    }

    /**
     * 获取未转换成bufferImage的图片
     *
     * @return 图片信息
     */
    private ImageProvider getFineImage() {
        if (this.image == null) {
            return null;
        }

        return this.image.getFineImage();
    }

    /**
     * 获取带后缀的图片
     *
     * @return 带后缀的图片
     */
    public ImageWithSuffix getImageWithSuffix() {
        return this.image;
    }

    /**
     * 设置图片背景所使用的图片
     *
     * @param img 背景所使用的图片
     */
    public void setImage(Image img) {
        this.image = ImageWithSuffix.build(img);
    }

    /**
     * 设置图片背景所使用的图片
     *
     * @param image 背景所使用的图片
     */
    public void setImage(ImageWithSuffix image) {
        this.image = image;
    }

    /**
     * 获取图片背景的布局方式
     * 可能的值包括Constants.IMAGE_DEFAULT、Constants.IMAGE_TILED、
     * Constants.IMAGE_CENTER和Constants.IMAGE_EXTEND
     * 默认为Constants.IMAGE_DEFAULT
     *
     * @return 图片背景布局
     */
    public int getLayout() {
        return this.layout;
    }

    public int getLayout4Draw() {
        return layout4Draw == Constants.IMAGE_CENTER ? this.layout : layout4Draw;
    }

    public void setLayout4Draw(int layout4Draw) {
        this.layout4Draw = layout4Draw;
    }

    /**
     * 设置图片背景的布局方式
     *
     * @param layout 布局方式
     * @see Constants#IMAGE_DEFAULT
     * @see Constants#IMAGE_TILED
     * @see Constants#IMAGE_CENTER
     * @see Constants#IMAGE_EXTEND
     */
    public void setLayout(int layout) {
        this.layout = layout;
    }

    public Point getCellPoint() {
        return cellPoint;
    }

    public void setCellPoint(Point cellPoint) {
        this.cellPoint = cellPoint;
    }

    /**
     * 根据指定的画图对象和几何图形来画图片背景
     *
     * @param g     画图对象
     * @param shape 几何图形
     */
    @Override
    public void paint(Graphics g, Shape shape) {
        if (this.getImage() == null || shape == null) {
            return;
        }

        Graphics2D g2d = (Graphics2D) g;

        Paint paint = g2d.getPaint();

        if (isPDFExport(g2d) || isPrint(g2d)) {
            //pdf画板的话使用原图放置到pdf文件中,这样pdf阅读器拿到的就是没有处理过的图片,会清晰一些
            //打印时的处理同pdf导出
            paintBackgroundImage(g2d, shape, StableUtils.isNotSupportARGB(g2d));
        } else {
            //其他画板的话将原图重绘一遍(固定死宽高),可能会模糊一些,但对于设计器而言,渲染会快一些,打开含高清大图的模板不会那么卡
            g2d.setPaint(createPaint(shape, StableUtils.isNotSupportARGB(g2d)));
            g2d.fill(shape);
        }

        g2d.setPaint(paint);
    }

    private boolean isPDFExport(Graphics2D g2d) {
        return g2d.getClass().getName().contains("PdfGraphics2D");
    }

    private boolean isPrint(Graphics2D g2d) {
        return g2d instanceof PrinterGraphics;
    }

    /**
     * 根据指定的画图对象和几何图形来画具有渐变色的边框的图片背景
     *
     * @param g     画图对象
     * @param shape 几何图形
     */
    @Override
    public void drawWithGradientLine(Graphics g, Shape shape) {
        if (this.getImage() == null || shape == null) {
            return;
        }
        Graphics2D g2d = (Graphics2D) g;

        Paint paint = g2d.getPaint();
        g2d.setPaint(createPaint(shape, StableUtils.isNotSupportARGB(g2d)));

        g2d.draw(shape);

        g2d.setPaint(paint);
    }

    /**
     * 布局变化
     *
     * @param width  背景宽度
     * @param height 背景高度
     */
    @Override
    public void layoutDidChange(int width, int height) {
        int imageLayout = layout;
        if (layout == 1) {
            imageLayout = StableUtils.changeImageLayout4Draw(getImage(), getLayout(), width, height);
        }
        setLayout4Draw(imageLayout);
    }

    private void paintBackgroundImage(Graphics2D g2d, Shape shape, boolean isNotSupportARGB) {
        Rectangle2D rec2D = shape.getBounds2D();
        if (rec2D.getWidth() <= 0 || rec2D.getHeight() <= 0) {
            return;
        }

        Image image = this.getImage();
        if (image instanceof BufferedImage) {
            dealWithNotDisplayProblemInARGB(g2d, (BufferedImage) image);
        }
        Shape oldClip = g2d.getClip();
        g2d.translate(rec2D.getX(), rec2D.getY());
        g2d.setClip(0, 0, (int) rec2D.getWidth(), (int) rec2D.getHeight());
        GraphHelper.paintImage(g2d, (int) rec2D.getWidth(), (int) rec2D.getHeight(), image,
                // 默认的话. 图片是从左上开始画
                this.getLayout4Draw(), Constants.LEFT, Constants.TOP, -1, -1, isNotSupportARGB);
        g2d.translate(-rec2D.getX(), -rec2D.getY());
        g2d.setClip(oldClip);
    }

    private Paint createPaint(Shape shape, boolean isNotSupportARGB) {
        Rectangle2D rec2D = shape.getBounds2D();
        if ((int) rec2D.getWidth() <= 0) {
            rec2D.setRect(rec2D.getX(), rec2D.getY(), rec2D.getWidth() + PAINT_WIDTH_OFFSET, rec2D.getHeight());
        }
        if ((int) rec2D.getHeight() <= 0) {
            rec2D.setRect(rec2D.getX(), rec2D.getY(), rec2D.getWidth(), rec2D.getHeight() + PAINT_HEIGHT_OFFSET);
        }
        //daniel:查了一下flash的ARGB有BUG,不能使用ARGB,653没有使用TexturePaint直接用GraphHelper画的所以没问题
        BufferedImage buffered = new BufferedImage((int) rec2D.getWidth(), (int) rec2D.getHeight(), isNotSupportARGB ? BufferedImage.TYPE_INT_RGB : TYPE_INT_ARGB);
        Graphics2D g2 = buffered.createGraphics();
        GraphHelper.paintImage(g2, (int) rec2D.getWidth(), (int) rec2D.getHeight(), this.getImage(),
                // 默认的话. 图片是从左上开始画
                this.getLayout4Draw(), Constants.LEFT, Constants.TOP, -1, -1, isNotSupportARGB);

        g2.dispose();

        dealWithNotDisplayProblemInARGB(g2, buffered);

        return new TexturePaint(buffered, rec2D);
    }

    private void dealWithNotDisplayProblemInARGB(Graphics2D g2d, BufferedImage buffered) {
        //不是pdf 绘制时不需要管
        if (!g2d.getClass().getName().endsWith("PdfGraphics2D")) {
            return;
        }
        // 当图片“只包含透明和纯黑像素点”的时候,用 ARGB,PDF 导出时会消失。
        // 只要有一个像素点不满足这个条件,图片就可以正常显示。
        int rgb = buffered.getRGB(0, 0);
        //-16777216 纯黑
        // 0 透明
        if (-16777216 == rgb || 0 == rgb) {
            buffered.setRGB(0, 0, rgb + 1);
        }
    }

    /**
     * Paints background of the shape and the background can move with the Scroll.
     * If the value is -1, use the actual size.
     * see paint(), this method has two more parameters.
     */
    /**
     * 画截取了指定宽度和高度的图片背景,用于滚动中动态更改背景
     *
     * @param g          画图对象
     * @param shape      几何形状
     * @param moveWidth  截取宽度
     * @param moveHeight 截取高度
     */
    public void paint4Scroll(Graphics g, Shape shape, int moveWidth, int moveHeight) {
        if (this.getImage() == null) {
            return;
        }

        Rectangle2D rec2D = shape.getBounds2D();

        Graphics2D g2d = (Graphics2D) g;
        Shape oldClip = g2d.getClip();
        g2d.clip(shape);
        g2d.translate(rec2D.getX(), rec2D.getY());

        GraphHelper.paintImageMoved(g2d, (int) rec2D.getWidth(), (int) rec2D.getHeight(), this.getImage(),
                //daniel 默认画靠近顶部
                this.getLayout(), Constants.LEFT, Constants.TOP, -1, -1,
                moveWidth, moveHeight, false);

        g2d.translate(-rec2D.getX(), -rec2D.getY());
        g2d.setClip(oldClip);
    }

    /**
     * 判断当前对象是否和指定的对象相等
     *
     * @param obj 指定的对象
     * @return 相等则返回true,不相等则返回false
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ImageBackground)) {
            return false;
        }

        ImageBackground newImageBackground = (ImageBackground) obj;
        return this.getLayout() == newImageBackground.getLayout()
                && BaseUtils.imageEquals(this.getFineImage(), newImageBackground.getFineImage());
    }

    /**
     * 将背景输出成JSON对象
     *
     * @return 表示背景的JSON对象
     * @throws JSONException
     */
    @Override
    public JSONObject toJSONObject() throws JSONException {
        JSONObject js = super.toJSONObject();

        js.put("layout", this.layout);
        //兼容老版本,在后续的版本中应该去除
        if (this.getFineImage() != null) {
            js.put("image", Base64.encode(this.getFineImage().getBytes()));
            if (this.getImage() != null) {
                js.put("imgWidth", this.getImage().getWidth(null));
                js.put("imgHeight", this.getImage().getHeight(null));
            }
        }

        return js;
    }

    @Override
    public JSONObject toJSONObject(Repository repo, Dimension size) throws JSONException {
        // MOBILE-10972 移动端直接拿原图.
        JSONObject result = this.toJSONObject();
        result.put("src", createImageURL(repo));
        return result;
    }

    @Override
    public JSONObject toJSONObject(Repository repo) throws JSONException {
        JSONObject result = this.toJSONObject();
        result.put("src", createImageURL(repo));
        return result;
    }

    /**
     * 图片背景在web端作为辨识的字符串
     *
     * @return 返回用于表示图片背景类型的字符串
     */
    @Override
    public String getBackgroundType() {
        return "ImageBackground";
    }

    @Override
    public Background readAdditionalAttr(XMLableReader reader) {
        //layout
        setLayout(reader.getAttrAsInt("layout", Constants.IMAGE_DEFAULT));

        reader.readXMLObject(new XMLReadable() {

            @Override
            public void readXML(XMLableReader reader) {
                if (reader.isChildNode()) {
                    if (reader.getTagName().equals(XMLConstants.Image_TAG)) { //image.
                        Image image = GeneralXMLTools.readImage(reader);
                        setImage(ImageWithSuffix.build(image));
                    } else if (XMLConstants.Deprecated_Image_TAG.equals(reader.getTagName())) {
                        Image image = GeneralXMLTools.deprecatedReadImage(reader);
                        setImage(ImageWithSuffix.build(image));
                    } else if (ImageWithSuffix.XML_TAG.equals(reader.getTagName())) {
                        ImageWithSuffix image = new ImageWithSuffix();
                        setImage((ImageWithSuffix) reader.readXMLObject(image));
                    }
                }
            }
        });
        return this;
    }

    @Override
    public void writeAdditionalAttr(XMLPrintWriter writer) {
        writer.attr("name", "ImageBackground");

        //width/height
        writer.attr("layout", getLayout());

        //image.
        if (this.image != null) {
            this.image.writeXML(writer);
        }
    }

    @Override
    public JSONObject createJSONConfig(Repository repo) throws JSONException {
        return createJSONConfig(repo, this.layout);
    }

    private JSONObject createJSONConfig(Repository repo, int layoutType) {
        StringBuffer sb = new StringBuffer();
        // 取出图片
        String url = createImageURL(repo);
        sb.append("url(").append(url).append(") ");
        sb.append(createImageLayoutDescription(layoutType));
        if (layoutType == Constants.IMAGE_ADJUST || layoutType == Constants.IMAGE_EXTEND) {
            return adjustBackgroundJson(repo, url, sb, layoutType);
        }

        return JSONObject.create().put("background", sb.toString());
    }

    private String createImageURL(Repository repo) {
        //使用url方式
        BackgroundImageDisplayModeProcessor processor = StableFactory.getMarkedInstanceObjectFromClass(BackgroundImageDisplayModeProcessor.XML_TAG,
                BackgroundImageDisplayModeProcessor.class);
        if (processor != null) {
            return processor.changeDisplayMode(repo, this.getFineImage(), BaseConstants.CHECKOUTIMAGE);
        }
        return repo.checkoutObject(this.getFineImage(), BaseConstants.CHECKOUTIMAGE);
    }


    private JSONObject adjustBackgroundJson(Repository repo, String url, StringBuffer sb, int type) throws JSONException {
        JSONObject jo = JSONObject.create();
        // 拉伸和自适应需要在web端再做处理
        if (repo.getBrowser().isLowIEVersion()) {
            jo.put("type", type);
            jo.put("url", url);
            // 这里生成参数面板背景的repo获取不到boxModel 而ie8杂项需要特别处理
            jo.put("background", sb.toString());
            jo.put("background-repeat", "no-repeat");
            jo.put("background-image", "none");
            jo.put("filter", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url + "',sizingMethod='scale')");
            return jo;
        }

        jo.put("background", sb.toString());
        switch (type) {
            case Constants.IMAGE_ADJUST:
                jo.put("background-size", "contain");
                jo.put("background-position", "center");
                break;
            case Constants.IMAGE_EXTEND:
                jo.put("background-size", "100% 100%");
                break;
            default:
                break;
        }

        return jo;
    }

    private static String createImageLayoutDescription(int layout) {
        ExtraClassManagerProvider pluginProvider = PluginModule.getAgent(PluginModule.ExtraCore);
        if (pluginProvider != null) {
            ImageLayoutDescriptionProcessor processor = pluginProvider.getSingle(ImageLayoutDescriptionProcessor.XML_TAG);
            if (processor != null) {
                String description = processor.getCustomLayoutDescription(layout);
                if (StringUtils.isNotEmpty(description)) {
                    return description;
                }
            }
        }

        switch (layout) {
            case Constants.IMAGE_DEFAULT:
                return "no-repeat";
            case Constants.IMAGE_ADJUST:
                return "no-repeat";
            case Constants.IMAGE_CENTER:
                return "no-repeat center";
            case Constants.IMAGE_TILED:
                return "repeat";
            case Constants.IMAGE_EXTEND:
                return "no-repeat center";
            default:
                return StringUtils.EMPTY;
        }
    }

    @Override
    public JSONObject createJSONConfig(Repository repo, int width, int height) throws JSONException {
        this.layoutDidChange(width, height);
        return this.createJSONConfig(repo, this.getLayout4Draw());
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        in.defaultReadObject();
        String format = in.readUTF();
        Object obj = in.readObject();
        if (obj instanceof ImageSerializable) {
            setImage(ImageWithSuffix.build(((ImageSerializable) obj).getImage(), format));
        }

    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        String format = this.image == null ? DEFAULT_IMAGE_FORMAT : this.image.getFormat();
        out.writeUTF(format);
        Image serialableTarget = getSeriableTarget(this.image);
        ImageSerializable imageSerializable = new ImageSerializable(serialableTarget, format);
        out.writeObject(imageSerializable);
    }

    private Image getSeriableTarget(ImageWithSuffix imageWithSuffix) {
        if (imageWithSuffix == null || imageWithSuffix.getImage() == null) {
            return new BufferedImage(1, 1, TYPE_INT_ARGB);
        }
        return imageWithSuffix.getImage();
    }
}


四、支持版本

产品线

版本

支持情况

备注

FR8.0支持
FR9.0支持
FR10.0支持
BI3.6支持
BI4.0支持
BI5.1支持
BI5.1.2支持
BI5.1.3支持

五、插件注册

Code Block
languagexml
themeEclipse
firstline1
titleplugin.xml
linenumberstrue
<extra-designer>
        <BackgroundQuickUIProvider class="your class name"/>
</extra-designer>

...