Skip to content

ofd转图片的2个改进建议 #77

Description

@kdevilpf

因为我写的是node.js版本,对python不太属性,无法提供python代码
1、有些图片显示前先要对图片进行裁剪图片显示才会正常,easyofd在2025.10.22日测试未对图片裁剪,相关xml代码如下(因为隐私问题,作者可从一级注册建筑师注册证书获取到类似这段xml):

<ofd:ImageObject ID="51" CTM="33.8667 0 0 45.1556 0 0" Boundary="135.5725 134.2143 33.8667 45.1556" ResourceID="50">
                <ofd:Clips TransFlag="false">
                    <ofd:Clip>
                        <ofd:Area CTM="0.3528 0 0 0.3528 0 0">
                            <ofd:Path Boundary="-5.75 0 122.3 111.25" Stroke="false" Fill="true">
                                <ofd:AbbreviatedData>M 0 0 L 122.3 0 L 122.3 111.25 L 0 111.25 C </ofd:AbbreviatedData>
                            </ofd:Path>
                        </ofd:Area>
                    </ofd:Clip>
                </ofd:Clips>
            </ofd:ImageObject>

js版本的参照代码

            var mm1 = 200 / 25.4; //200dpi
            var canvas = document.getElementById("canvasid");
            canvas.width = mm1 * 210;
            canvas.height = mm1 * 297;
            var ctx = canvas.getContext("2d");
            
            ctx.save()
            ctx.beginPath()
            let imgrect = "135.5725 134.2143 33.8667 45.1556".split(" ")
            let Boundary = "-5.75 0 122.3 111.25".split(" ")
            ctx.translate(Math.ceil(Number(imgrect[0]) * mm1), Math.ceil(Number(imgrect[1]) * mm1))
            ctx.transform(0.3528, 0, 0, 0.3528, 0, 0)
            let clipsPath = "M 0 0 L 122.3 0 L 122.3 111.25 L 0 111.25 C ".split(" ")
            for (let j = 0; j < clipsPath.length;) {
                switch (clipsPath[j]) {
                    case "M":
                        ctx.moveTo(Math.ceil((Number(Boundary[0]) + Number(clipsPath[j + 1])) * mm1), Math.ceil((Number(Boundary[1]) + Number(clipsPath[j + 2])) * mm1))
                        j += 3
                        break;
                    case "L":
                        ctx.lineTo(Math.ceil((Number(Boundary[0]) + Number(clipsPath[j + 1])) * mm1), Math.ceil((Number(Boundary[1]) + Number(clipsPath[j + 2])) * mm1))
                        j += 3
                        break;
                    case "Q":
                        ctx.quadraticCurveTo((Number(Boundary[0]) + Number(clipsPath[j + 1])) * mm1, (Number(Boundary[1]) + Number(clipsPath[j + 2])) * mm1, (Number(Boundary[0]) + Number(clipsPath[j + 3])) * mm1, (Number(Boundary[1]) + Number(clipsPath[j + 4])) * mm1)
                        j += 5
                        break;
                    case "B":
                        ctx.bezierCurveTo((Number(Boundary[0]) + Number(clipsPath[j + 1])) * mm1, (Number(Boundary[1]) + Number(clipsPath[j + 2])) * mm1, (Number(Boundary[0]) + Number(clipsPath[j + 3])) * mm1, (Number(Boundary[1]) + Number(clipsPath[j + 4])) * mm1, (Number(Boundary[0]) + Number(clipsPath[j + 5])) * mm1, (Number(Boundary[1]) + Number(clipsPath[j + 6])) * mm1)
                        j += 7
                        break;
                    case "S":
                        j += 3 //还未实现
                        break;
                    case "A":  //还未实现
                        j += 8
                        break;
                    case "C":
                        ctx.closePath()
                        j++
                        break;
                    default:
                        j++
                        break;
                }

            }
            ctx.clip()//创建裁剪区域
            let ctm = "33.8667 0 0 45.1556 0 0".split(" ")
            ctx.setTransform(Number(ctm[0]) / Number(imgrect[2]), 0, 0, Number(ctm[3]) / Number(imgrect[3]), Math.ceil(Number(ctm[4]) * mm1), 
            Math.ceil(Number(ctm[5]) * mm1));
            ctx.translate(Math.ceil(Number(imgrect[0]) * mm1), Math.ceil(Number(imgrect[1]) * mm1))
            ctx.drawImage(img, 0, 0); //img为ResourceID="50"的图片
            ctx.restore()

2.有些文字大小及位置显示不正常,同样参照一级注册建筑师注册证书

<ofd:TextObject ID="10" CTM="0.3528 0 0 0.3528 0 0" Boundary="105.5864 54.1973 71.1101 11.3002" Font="9" Size="28">
                <ofd:FillColor Value="153 51 0"/>
                <ofd:CGTransform CodePosition="0" CodeCount="7" GlyphCount="7">
                    <ofd:Glyphs>2 3 4 5 6 7 8</ofd:Glyphs>
                </ofd:CGTransform>
                <ofd:TextCode X="0" Y="26.208" DeltaX="28 28 28 28 28 28">一级注册建筑师</ofd:TextCode>
            </ofd:TextObject>

对于如上带CTM变换矩阵、DeltaX或者DeltaY、还带ofd:CGTransform标签的文字,使用opentype库逐个把文字画出来,就不会出现文字大小不一的情况。下面是我的屎山代码。

if (ctm) {
//带CTM的情况
                        ctx.save()
                        ctx.translate(Math.ceil((Number(Boundary[0])) * mm1), Math.ceil((Number(Boundary[1])) * mm1))
                        ctx.transform(Number(ctm[0]), Number(ctm[1]), Number(ctm[2]), Number(ctm[3]), Math.ceil(Number(ctm[4]) * mm1), Math.ceil(Number(ctm[5]) * mm1));
                        let addx = Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0) * mm1
                        let addy = Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0) * mm1
                        //有逐字控制位置的情况
                        if (DeltaX || DeltaY) {
                            //有CGTransform标签,那么用opentype一个字一个字的画出来
                            if (CGTransform) {
                                for (let j = 0; j < textlength; j++) {
                                    addx += Number(DeltaXarray[j]) * mm1
                                    addy += Number(DeltaYarray[j]) * mm1
                                    const font = await opentype.load(`./font/${FontsMap[textmessage?.[':@']?.['Font']]?.FontFile?.__text}`);
                                    const path = font.getPath(String(TextCode?.['ofd:TextCode']?.[0]?.['#text'])[j], addx, addy, fontSize);
                                    path.fill = ctx.fillStyle;
                                    path.draw(ctx);
                                }
                            } else {
                                for (let j = 0; j < textlength; j++) {
                                    addx += Number(DeltaXarray[j]) * mm1
                                    addy += Number(DeltaYarray[j]) * mm1
                                    textmessage?.[':@']?.Stroke ? ctx.strokeText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text'])[j], addx, addy) : null;
                                    ctx.fillText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text'])[j], addx, addy);
                                }
                            }
                        } else {
                            textmessage?.[':@']?.Stroke ? ctx.strokeText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text']), Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0) * mm1, Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0) * mm1) : null;
                            ctx.fillText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text']), Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0) * mm1, Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0) * mm1);
                        }
                        ctx.restore()
                    } else {
                        //不带变换就正常的输出文字
                        let addx = (Number(Boundary[0]) + Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0)) * mm1
                        let addy = (Number(Boundary[1]) + Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0)) * mm1
                        //console.log("YYYYYYYY",addx,addy)
                        if (DeltaX || DeltaY) {
                            for (let j = 0; j < textlength; j++) {
                                ctx.save()
                                //ctx.scale(0.6, 1);
                                addx += Number(DeltaXarray[j]) * mm1
                                addy += Number(DeltaYarray[j]) * mm1
                                // 移动到当前字的位置
                                ctx.translate(addx, addy);
                                // 缩放文字
                                ctx.scale(textmessage?.[':@']?.HScale ? Number(textmessage?.[':@']?.HScale) : 1, 1);
                                textmessage?.[':@']?.Stroke ? ctx.strokeText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text'])[j], 0, 0) : null;
                                ctx.fillText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text'])[j], 0, 0);
                                ctx.restore()
                            }
                        } else {
                            textmessage?.[':@']?.Stroke ? ctx.strokeText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text']), (Number(Boundary[0]) + Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0)) * mm1, (Number(Boundary[1]) + Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0)) * mm1) : null;
                            ctx.fillText(String(TextCode?.['ofd:TextCode']?.[0]?.['#text']), (Number(Boundary[0]) + Number(TextCode?.[':@']?.X ? TextCode?.[':@']?.X : 0)) * mm1, (Number(Boundary[1]) + Number(TextCode?.[':@']?.Y ? TextCode?.[':@']?.Y : 0)) * mm1);
                            ctx.restore()
                        }

                    }

最后提醒一下,PublicRes.xml,里面FontName的字体名字建议不要直接采用,例如这个里面SimSun是用了自带的字体文件font_11_11.ttf,如果向画图系统注册了字体font_11_11.ttf,如果没改名,再画出宋体,可能会直接调用font_11_11.ttf导致出现奇怪造型的字体。

<?xml version="1.0" encoding="UTF-8"?>
<ofd:Res xmlns:ofd="http://www.ofdspec.org/2016" BaseLoc="Res">
    <ofd:Fonts>
        <ofd:Font ID="9" FontName="SimHei">
            <ofd:FontFile>font_9_9.ttf</ofd:FontFile>
        </ofd:Font>
        <ofd:Font ID="11" FontName="SimSun">
            <ofd:FontFile>font_11_11.ttf</ofd:FontFile>
        </ofd:Font>
        <ofd:Font ID="15" FontName="SimSun">
            <ofd:FontFile>font_11_11.ttf</ofd:FontFile>
        </ofd:Font>
        <ofd:Font ID="58" FontName="宋体" FamilyName="宋体"/>
    </ofd:Fonts>
</ofd:Res>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions