新しいレイアウトファイルのスキーマ
hidakatsuya opened this issue · comments
これは v0.9.0 における 機能の実装コンセプト であり、この内容を基にユーザからのフィードバックを得てよりよい仕様を議論し、導き出すことが目的。 コメント欄での議論により、内容は適宜更新される。また、技術的な制約など、実際の機能が必ずしもコンセプト通りになるとは限らない。
モチベーション
現状
現在のレイアウトファイルには下記のようなデータ構造でレイアウト情報が記録されている:
{
"version": "0.0.0",
"config": {
"title": "Report Title",
"page": {
"paper-type": "A4",
"width": 0.0,
"height": 0.0,
"orientation": "landscape",
"margin-top": 0.0,
"margin-bottom": 0.0,
"margin-left": 0.0,
"margin-right": 0.0,
}
},
"svg": "<svg><g class=\"canvas\">...</g></svg>",
"state": {
"layout-guide": [{ "type": "x", "position": 0.0 },,,],
}
}
:
"svg": {
"<svg>
<g class=\"canvas\">
<!--LAYOUT<rect x-id=\"rect_id\" x=\"100.0\" y=\"100.0\" width=\"200\" height=\"200\"/>-->
<!--SHAPE{ "type": "s-text", "id": "rect_id", "display": "true", "svg": { "attrs": {...} }}SHAPE-->
</g>
</svg>"
}
:
- 図形の情報を SVG としてそのまま記録
- これに加えて、Generator 用に各図形の情報を JSON 化して記録
- 上記を区別するために、それぞれ
<!--LYAOUT ... LAYOUT-->
<!--SHAPE ... SHAPE-->
で括る
図形の情報を SVG そのままに記録しているのは、Editor でレイアウトを開く際に簡単に復元できるようにするためだが、 Generator で各図形の情報を読み取りやすくするため、レイアウト保存時に各図形の情報を JSON として付与する形で記録している。
現在のレイアウトファイル .tlf
内の仕様は Spec: Layout File と Spec: SVG and Shapes で公開している。
問題点
- 機能の改善などの歴史的経緯や互換性維持のために、SVG に冗長な属性が含まれていたり、無駄な情報が混在しており Editor/Generator の実装を複雑なものにしている
- 各図形の情報を表現する JSON 構造に SVG の属性値とそれ以外の属性値が混在しており、構造があいまいで非常に扱いにくく、かつ分かりにくい
例: テキストオブジェクトの JSON スキーマ
{
"type": "s-text",
"id": "text_id",
"display": "true",
"line-height": 20.0,
"line-height-ratio": 2.5,
"valign": "top",
"box": {
"x": 100.0,
"y": 100.0,
"width": 100.0,
"height": 100.0
},
"text": ["line1", "line2"],
"svg": {
"attrs": {
"text-anchor": "start",
"font-family": "Helvetica",
"font-size": "18",
"character-spacing": "3.0",
:
}
}
}
- SVG (テンプレート) に強く依存していることで、Editor の実装もテンプレートとロジックが強く結合し、コードの見通しが非常に悪い
- 同様に、今後、コンポーネント志向でのリファクタリングあるいは書き直しをする上での障壁になる
- サードパーティソフトウェアなど外部のツールがレイアウト情報を活用したり連携することが困難
- Diff が非常に取りづらい
Schema の刷新
方針
- 全ての情報を JSON で記録
- SVG の構造や属性の内包、SVG との依存を完全に排除
- 各オブジェクト(図形)の JSON スキーマも一新し、統一された理解しやすい構造へ改善
- Minify ではなく Pretty 形式で保存し、人間にも理解しやすく Diff も取りやすくする
互換性について
Thinreports のバージョン | 新しいスキーマ(Editor v0.9 以降で作成)の読み込み | 古いスキーマ(Editor v0.9 未満で作成)の読み込み |
---|---|---|
0.9 未満 | x | o |
0.9, 1.0 | o | o |
1.1 以上 | o | x |
レイアウトファイルの新しいスキーマ
メインスキーマ
{
"version": "0.0.0",
"title": "Report Title",
"report": {
"paper-type": "A4",
"width": 0.0,
"height": 0.0,
"orientation": "landscape",
"margin": [0.0, 0.0, 0.0, 0.0]
},
"state": {
"layout-guides": [
{ "type": "x", "position": 0.0 }
]
},
"items": [
{ "type": "rect", "id": "", "x": 0.0, "y": 0.0, "width": 0.0, "height": 0.0, "style": { "stroke-width": 1 }},
{ "type": "text-block", "id": "price", "x": 0.0, "y": 0.0, },
{ },
]
}
"items":[]
には、レイヤー順(降順)で各オブジェクト(図形)の JSON 情報を順番に記録- 不要なネスト構造を排除し、極力見やすい構造へ
オブジェクト(図形)スキーマ
概要
- スタイルの情報は全て
style
内に記録 - プロパティの省略は許可しない。必ずスキーマにあるプロパティを持つ
Rect
{
"id": "",
"type": "rect",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"rx": 0.0,
"ry": 0.0,
"description": "Description for shape",
"display": true,
"style": {
"border-width": 1,
"border-color": "#000000",
"border-style": "dashed",
"fill-color": "#ffffff"
}
}
Ellipse
{
"id": "",
"type": "ellipse",
"cx": 0.0,
"cy": 0.0,
"rx": 0.0,
"ry": 0.0,
"description": "Description for shape",
"display": true,
"style": {
"border-width": 1,
"border-color": "#000000",
"border-style": "dashed",
"fill-color": "#ffffff"
}
}
Line
{
"id": "",
"type": "line",
"x1": 0.0,
"y2": 0.0,
"x2": 0.0,
"y2": 0.0,
"description": "Description for shape",
"display": true,
"style": {
"border-width": 1,
"border-color": "#000000",
"border-style": "dashed",
}
}
Text
{
"id": "",
"type": "text",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"display": true,
"texts": [
"text line 1st",
"text line 2nd"
],
"style": {
"font-family": ["Helvetica"],
"font-size": 18,
"color": "#000000",
"font-style": [ "bold", "italic", "linethrough", "underline" ],
"text-align": "left",
"vertical-align": "top",
"line-height": 30,
"line-height-ratio": 1.5,
"letter-spacing": "normal"
}
}
Image
{
"id": "",
"type": "image",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"data": {
"mime-type": "image/png",
"base64": "AAAAAAAAAAAAAAA..."
},
"display": true
}
Text block
{
"id": "text_block_id",
"reference-id": "",
"type": "text-block",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"value": "default value",
"mulitple-line": true,
"format": {
"base": "Created at {value}",
"type": "datetime",
"datetime": {
"format": "%Y-%m-%d %H:%M:%S"
}
},
"display": true,
"style": {
"font-family": ["Helvetica"],
"font-size": 18,
"color": "#000000",
"font-style": [ "bold", "italic", "linethrough", "underline" ],
"text-align": "left",
"vertical-align": "top",
"line-height": 30,
"line-height-ratio": 1.5,
"letter-spacing": "normal",
"overflow": "truncate",
"word-wrap": "break-word"
}
}
数値書式:
{
"format": {
"number": {
"delimiter": ",",
"precision": 3
}
}
}
字詰め書式:
{
"format": {
"padding": {
"length": 5,
"char": "0",
"direction": "L"
}
}
}
Image block
{
"id": "image_block_id",
"type": "image-block",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"display": true,
"style": {
"position-x": "left",
"position-y": "top"
}
}
Page number
{
"id": "",
"type": "page-number",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"format": "{page} / {total}",
"target": "report",
"display": true,
"style": {
"font-family": ["Helvetica"],
"font-size": 18,
"color": "#000000",
"font-style": [ "bold", "italic", "linethrough", "underline" ],
"text-align": "left",
"line-height": 30.0,
"line-height-ratio": 1.5,
"letter-spacing": "normal",
"overflow": "truncate"
}
}
List
{
"id": "default",
"type": "list",
"x": 100.0,
"y": 100.0,
"width": 200.0,
"height": 300.0,
"description": "Description for shape",
"content-height": 190.0,
"auto-page-break": true,
"display": true,
"header": {
"enabled": true,
"height": 10.0,
"translate": { "x": 100.0, "y": 100.0 },
"items": [
{ "type": "rect", "id": "" },
{ "type": "text", "id": "" }
]
},
"detail": {
"height": 10.0,
"translate": { "x": 100.0, "y": 100.0 },
"items": [
{ "type": "rect", "id": "" },
{ "type": "text", "id": "" }
]
},
"page-footer": {
"enabled": true,
"height": 10.0,
"translate": { "x": 100.0, "y": 100.0 },
"items": [
{ "type": "rect", "id": "" },
{ "type": "text", "id": "" }
]
},
"footer": {
"enabled": true,
"height": 10.0,
"translate": { "x": 100.0, "y": 100.0 },
"items": [
{ "type": "rect", "id": "" },
{ "type": "text", "id": "" }
]
}
}
- 各セクションの
"items: []"
には、オブジェクト(図形)の JSON スキーマをレイヤー降順で格納
This concept was frozen, I have started to develop.
thinreports/thinreports-basic-editor#56
Image の data
ですが、以下のようにデータと MIME-Type を分けて持ちたいと思いますがどうでしょう?理由は、まず base64 データを抜き出す必要がないのと、同じように画像タイプを知ることも簡単になるからです。また、キー名を base64-data
などにすることができるので、原案の data
よりもその値が Base64 データということが明確になります。
{
"id": "",
"type": "image",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"base64-data": "AAAAAAAAAAAAAAA",
"mime-type": "image/png",
"style": {
"display": true
}
}
「MIME-Typeを分けて持ちたい」というのはOKだと思います。
気になるのはSchemaの構造ですが、 data URI scheme の構成を分解した構造にすると良いかなと思います。
{
"id": "",
"type": "image",
"x": 0.0,
"y": 0.0,
"width": 0.0,
"height": 0.0,
"description": "Description for shape",
"data": {
"mime-type": "image/png",
"base64": "AAAAAAAAAAAAAAA"
},
"style": {
"display": true
}
}
気になるのはSchemaの構造ですが、 data URI scheme の構成を分解した構造にすると良いかなと思います。
良いですね。FROZEN_CONCEPT ですが、そうしましょう。後ほど修正しておきます。
下記の通り、Image スキーマの data
配下に base64
と mime-type
を置き、それぞれ Base64 データと画像の MIME-Type を定義するよう変更しました。
before:
{
"data": "image/png;base64,AAAAAAAA..."
}
after:
{
"data": {
"mime-type": "image/png",
"base64": "AAAAAAAAA..."
}
}
FROZEN_CONCEPT
なのに 2度目の仕様変更になりますが Pretty フォーマットな JSON スキーマで出力する仕様を追加しました。
layout-guide
を layout-guides
に修正しました。
display
を style
の中ではなく外(top-level)で定義することにしました。
objects
を items
へ変更しました。