善良な若者が三頭蛇と戦った方法、またはAdobe InDesignドキュメントにSVGグラフィックスを埋め込む方法の話-パート1

すべてのhabrazhitelへのご挨拶!

最初に少し余談。 この投稿は私によって書かれたのではなく、 まだ原理に基づいていない updによって書かれました: viklequickはすでに頑固で 、私の意見では、すべての結果であなたの注意に値します。 だから...



原因



画像 凍えるような冬の季節になると、 著者はEPS形式のベクター画像と戦う良い仲間にうんざりしまし 。 そして、彼は進捗状況を把握し、SVGの形式でAdobe InDesignドキュメントにグラフとチャートを埋め込むことにしました。 アドビシステムズはAdobe Flashを好んでおり、Adobe InDesignではSVGがサポートされていないため、大きな失望が彼に降りかかりました。 しかし、良い仲間はInDesignのプラグインを作成する際に顕著な経験を蓄積しており、彼は強力な英雄を使用して3頭のヒドラを手に入れることにしました。 戦士は言った-戦士がやった、つまり-彼はそれを取った。

この闘争の詳細について、私たちは物語をリードします。



物語



ステップ1、または上腕三頭筋の解剖学の研究



画像 開始するには、InDesignが写真でどのように機能するかを簡単に説明する必要があります。

InDesignの観点から見ると、画像は自分自身を描画して印刷できる特別な種類のPageItemです。 また、このPageItemは、フレーム内またはテーブルセル内にインラインでページに配置でき、これらすべてを処理する必要があります。 それでも、3種類のレンダリングがあります-高速(交差した長方形が描かれます)、最適(ラスタープロキシイメージ)、完全(低速)です。 このすべてをサポートすることも必要です。

したがって、次のようなImport Plug-in、Page Item、およびいくつかのヘルパークラスがあります。



 クラス
 {
   kSVGItem、
   kDisplayListPageItemBoss、
   {
     IID_ISHAPE、kSVGShapeImpl、
     IID_IINKRESOURCES、kAllProcessInkResourcesImpl、
     IID_IFLATTENERUSAGE、kSVGItemFlattenerUsageImpl、
     IID_IVISITORHELPER、kEPSItemVisitorHelperImpl、
     IID_ISCRIPT、kSVGItemScriptImpl、
   }
 }、




など:



Class

{

kSVGPlaceProviderBoss,

kInvalidClass,

{

IID_IK2SERVICEPROVIDER, kImportServiceImpl,

IID_IIMPORTPROVIDER, kSVGPlaceProviderImpl,

IID_IIMPORTPREVIEW, kSVGImportPreviewImpl,

}

},









そしてもちろん、起動/シャットダウン用の標準ヘッダーもありますが、これは意味をなさないものです。

それが何で、なぜなのか見てみましょう。

•IID_ISHAPEは、レンダリングを処理する実際のページアイテムの実装です。

•IID_IINKRESOURCES-外部ファイル(インク)へのリンクを処理します。

•IID_IFLATTENERUSAGE-アルファチャネルラスタライズをPDFで処理します。 実際、これはフラットナーを含めることを明確に要求する1行であり、それ以上は何もありません。

•IID_IVISITORHELPER-EPSから借用して標準を維持します。

•IID_ISCRIPT-スクリプトを通じて要素のサポートを提供します。 これも非常に単純な部分であり、オブジェクトのタイプを正しく指定するだけです。

•IID_IK2SERVICEPROVIDERおよびIID_IIMPORTPROVIDER-Placeコマンドのサポートを追加します。

•IID_IIMPORTPREVIEW-ファイル選択ダイアログでプレビューを提供します(Windows固有)。

最も興味深いのは実際にはIID_ISHAPEです。これがプラグインの中心です。 彼は、IGraphicsPortを使用して直接描画する能力と、ラスタープロキシイメージを取得する能力を必要とします。 将来を見据えて、私は注意したい-十分な驚きがあった。



ステップ2、またはヒロイックソードを選択



画像 エクスプレスグーグル、およびOpenClipArtのサンプル画像を表示することで、Apache Batikにとどまることを考えました。 私はすぐにバティック自体を修正するためにいくつかの努力をしなければならなかったと言わなければならないが、結局彼らは取るに足らないものであることが判明した。 基本的には「コンテンツごとにSVGのバージョンを推測する」に要約されます。 ただし、「IGraphicsPortを使用して絵を描く方法」というタスクはバティックに登場し、最も難しいものとして焦点を当てます。 そして、退屈なC ++のものから楽しいJavaクリエイティブへ、そしてその逆に移行します。

しかし、恥ずかしいミンストレルは私たちに教えてくれます-あなた、ヒーロー、間違った物語にさまよいました、そしてあなたはヘビとハリネズミを横切るときいつも起こるように、あなたは5メートルの有刺鉄線で終わるでしょう。 困難な過ちの息子である経験が私たちに教えてくれます。この場合、もっと役に立つものが得られます。



ステップ3、または開始

前述のように、デザインストリームの操作方法をJavaに教える必要があります。 実際、タスクは明白であり、JNIを介して解決されます。

画像

/**

* Class for reading from InDesign IPMStream

*/

public class PMInputStream extends InputStream

{

/**

* Creates PMInputStream object and attaches it to IPMStream* already

* opened by InDesign.

* @param iPMStreamPtr IPMStream* to attach to

* @throws IOException The stream has no ability to read.

*/

public PMInputStream(long iPMStreamPtr) throws IOException {

this.ownedStreamPtr = iPMStreamPtr;

this.retain();

}



@Override

public int read() throws IOException {

return readByte();

}



@Override

public int read(byte b[], int off, int len) throws IOException {

if (len == 0) return 0;

return readBytes(b, off, len);

}



@Override

public long skip(long n) throws IOException {

return performSeek(n, SeekFromCurrent);

}



public long seek(long numberOfBytes, int fromHere) throws IOException {

return performSeek(numberOfBytes, fromHere);

}



@Override

public int available() {

return availableBytes();

}



// native functions below

...









実装はネイティブ関数を介して構築されます。それらをリストします。 それらは自明であり、特別なコメントを必要としません。 興味深い点が1つだけあります。ここにあります:



/**

* 'Cause IPMStream is a COM-like object we gotta call AddRef() for it

* if we wanna save pointer in our class.

*/

private native void retain();

/**

* Once we called AddRef() we shouldn't forget calling corresponding

* Release(). Let's do it!

*/

private native void release();









そしてリスト自体:



protected native int readByte();

protected native int readBytes(byte b[], int off, int len);

protected native long performSeek(long numberOfBytes, int fromHere);

protected native int availableBytes();



protected native String getFileName();

protected native long getLastModifiedTime();









最後の2つの関数は、レンダリングごとにDOMモデルを再構築しないように、Javaで直接要素をキャッシュするために追加されました。 必要なのは、データの変更を追跡し、変更時にそれらをすばやく読み直すことだけです。

そして最後の仕上げは、ネイティブメソッドの実際の実装です。



class IPMStream; class PMInputStream

{

private:

IPMStream* fStream;



public:

PMInputStream(IPMStream* stream);

~PMInputStream() { close(); }



void close();



int read() {

unsigned char result;

return read(&result, 1) == 1? result: -1;

}



int read(unsigned char* buffer, int len){ return fStream->XferByte(buffer, len); }

XInt64 seek(XInt64 numberOfBytes, SeekFrom fromHere) {

return fStream->Seek((int32)numberOfBytes, fromHere);

}



public:

enum SeekFrom { SeekFromStart = kSeekFromStart,

SeekFromCurrent = kSeekFromCurrent,

SeekFromEnd = kSeekFromEnd

};

};









すでにSVGに到達していますが、今の仕事は絵を取得して描くことです。 実際に、BufferedImageでベクター画像を描画した後、プロキシ画像を取得します。 バティックの標準的な例は、それがどのように取得されたかを明確に示しているため、スペースを節約するために、このフットクロスをここに持ち込まないでください。 PixelGrabberを使用して、BufferedImageからRGBのバイト配列を取得することも明らかです。 結果の配列を、記事の2番目のパートで説明したのと同じ方法でAGMImageRecordに渡します。

興味深いことに、BufferedImageではなくIGraphicsPortでのレンダリングは、標準のオフスクリーンGraphics2Dではなく、実装を置き換える必要がある1行だけ異なります。 これが私たちがやることです。



4番目のステップ、または暗黙のうちに電流を剣に接続して火花が飛ぶようにする

Graphics2Dの有能な継承のためのコードの量は大きく、退屈ですが、明らかであり、同じApache Batikから簡単に覗くことができます。 そのため、最も興味深い部分に焦点を当てますが、ここではPaint getPaint(){ return paint; }



Paint getPaint(){ return paint; }



省略します。

一般に、この種のプラグを安全に挿入できることに注意してください。



public void setXORMode(Color c1){ throw new RuntimeException("setXORMode: N/A"); }







など:



public void fillRect(int x, int y, int w, int h) { fill(new Rectangle(x, y, w, h)); }







このような単純化と重複の排除の結果、次の実際の機能が得られます。



public void draw(Shape s);

public void fill(Shape s);

public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer);



public void drawString(String str, float x, float y);

public void drawString(AttributedCharacterIterator iterator, float x, float y);









StrokeTextPainterを使用してさらに進むことができ、このセットは3つの関数に削減されます。これは良いことです。

次に、反対側にアプローチして、IGraphicsPortから必要なものを見てみましょう。



private native void newpath();

private native void moveto(float x, float y);

private native void lineto(float x, float y);

private native void curveto(float x1, float y1, float x2, float y2, float x3, float y3);

private native void curvetov(float x2, float y2, float x3, float y3);

private native void closepath();



private native void gsave();

private native void grestore();



private native void setlinewidth(float width);

private native void setdash(int len, float[] dashArray, float offset);

private native void setopacity(float opacity, boolean bIsAlphaShape); // from 0 to 1.0

private native void setrgbcolor(float r, float g, float b);



private native void fill();

private native void eofill();

private native void stroke();



private native void image(int[] buffer, int x, int y, int width, int height, double[] transformMatrix);



private native void clip();

private native void eoclip();









経験豊富な外観では、グラデーションなどのレンダリングツールが明らかにないことがわかります。 残念ながら、AdobeのAPIのこの部分のドキュメントは非常に少ないため、利用可能な機能を使用することはできませんでした。

どうやら、少し必要です。 ただし、新しいタスクが発生します。この全体を何らかの形で結合する必要があります。 単純なものから始めましょう-幾何学的な形で。

ここで最も難しい部分は、図形をJavaから一連のネイティブ関数にデプロイすることです。 これを行うには、標準のPathIteratorを使用しますが、同時にBatikはAffineTransformを非常に積極的に使用するため、変換についても忘れないでください。



private void applyPath(PathIterator pi, int kind) {

float[] coord = new float[6];

int retSeg;



newpath();

while(!pi.isDone()) {

retSeg = pi.currentSegment(coord);

switch (retSeg) {

case SEG_LINETO:

lineto(coord[0], coord[1]);

break;

case SEG_CUBICTO:

curveto(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]);

break;

case SEG_MOVETO:

moveto(coord[0], coord[1]);

break;

case SEG_QUADTO:

curvetov(coord[0], coord[1], coord[2], coord[3]);

break;

case SEG_CLOSE:

closepath();

break;

}

pi.next();

}



if(kind == PATH_FILL) {

if(pi.getWindingRule() == PathIterator.WIND_EVEN_ODD) eofill();

else fill();

}

else if(kind == PATH_STROKE) stroke();

else if(kind == PATH_CLIP) {

if(pi.getWindingRule() == PathIterator.WIND_EVEN_ODD) eoclip();

else clip();

}

}









これで、プリミティブを簡単に描画できます。



画像



public void draw(Shape s) {

PathIterator pi = s.getPathIterator(getTransform());

applyClip(getClip());

applyStroke(s);

applyStyles();

applyPath(pi, PATH_STROKE);

restoreClip();

}









これまでのところ、あらゆる種類の適用に焦点を当てるわけではありません*、それらはさらに議論されています。 厳密に言えば、fill()は、勾配に関するいくつかの困難を除いて、まったく同じに見えます。 ご想像のとおり、applyClip()も同様に実装されています。



private void applyClip(Shape xclip) {

gsave();

if(xclip != null) {

PathIterator pi = xclip.getPathIterator(getTransform());

applyPath(pi, PATH_CLIP);

}

}









実際のコードはもう少し複雑です。IGraphicsPortのフラットナー実装の機能を考慮し、パスが反時計回りになるように必要に応じてバイパスを回す必要があります。 また、前述のように、Batikはテキストをグリフのセットにベクトル化する方法を知っているため、フォントの操作を心配する必要はありません。 したがって、私たちのテキストは必然的に一連の幾何学的図形になります。



継続するには...



All Articles