-
Notifications
You must be signed in to change notification settings - Fork 1
/
DocPageContainer.ts
252 lines (218 loc) · 9.18 KB
/
DocPageContainer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
type ContainerType = 'view'|'image'|'text'|'textarea'|'table'
import { Page, DocUnits, WidthHeightInput, isWidthHeightInput, ModelUnits } from './internal'
//// TYPES AND INTERFACES ////
export type ContainerAlignment = 'center'|'top'|'left'|'right'|'bottom'|'topleft'|'topright'|'bottomleft'|'bottomright'
export type ContainerSide = 'width'|'height'
export type ZoomRelativeTo = 'container'|'world'
export type ScaleInput = 'auto'|number;
export type ContainerSizeRelativeTo = 'page' | 'page-content-area'; // page-content area is page without the padding on both sides
export type Position = Array<number|number>|ContainerAlignment
export type ContainerData = { // Combine all Container types for convenience
_entity:string
name:string
parent:string
type:ContainerType
width:number // relative to (see: widthRelativeTo)
widthRelativeTo:ContainerSizeRelativeTo
widthAbs?:number // in doc units (added on place)
height:number // relative to (see: widthRelativeTo)
heightRelativeTo:ContainerSizeRelativeTo
heightAbs?:number // in doc units (added on place)
position:Array<number|number> // relative to page-content-area
pivot:Array<number|number>
frame?:any // TODO
index?:number
caption?:string
contentAlign:ContainerAlignment
content:any; // TODO: raw content
zoomLevel:ScaleInput, // number or 'auto
zoomRelativeTo:ZoomRelativeTo,
docUnits:DocUnits,
modelUnits:ModelUnits,
_domElem?:HTMLDivElement, // added on placement
}
export interface Frame {
color:string // TODO
thickness:number
shape:'rect'|'circle'
}
export interface ContainerContent
{
settings:{[key:string]:any}
main:any; // main data
}
//// TYPEGUARDS
export function isContainerAlignment(o:any): o is ContainerAlignment
{
return ['center','top','left','right','bottom','topleft','topright','bottomleft','bottomright'].includes(o)
}
export function isPosition(o:any): o is Position
{
return (Array.isArray(o) && o.length === 2 && o.every(e => typeof e === 'number'))
|| isContainerAlignment(o);
}
export function isScaleInput(o:any): o is ScaleInput {
return (typeof o === 'string' && o === 'auto') || (typeof o === 'number')
}
//// CONTAINER CLASS
export class Container
{
//// SETTINGS ////
WIDTH_DEFAULT = 1.0; // in perc of content area
HEIGHT_DEFAULT = 1.0;
PIVOT_DEFAULT:ContainerAlignment = 'center';
POSITION_DEFAULT:ContainerAlignment = 'center';
ALIGNMENT_TO_WPERC_HPERC = { // NOTE: [0,0] is at leftbottom
'center' : [0.5,0.5],
'top' : [0,1],
'left' : [0,0.5],
'right' : [1,0.5],
'bottom' : [0.5,0],
'topleft' : [0,1],
'topright' : [1,1],
'bottomleft' : [0,0],
'bottomright' : [1,0],
}
//// END SETTINGS ////
name:string;
_page:Page; // through page also root doc
_parent:Container; // if nested on other container
_type:ContainerType;
_width:number; // in relative coordinates [0-1] of page content area width or page width (when _widthRelativeTo = 'page')
_height:number; // in relative coordinates [0-1] of page content area height or page height (when _heightRelativeTo = 'page')
_widthRelativeTo:ContainerSizeRelativeTo = 'page-content-area'; // NOTE: 'page-content-area is the area that remains after page padding [DEFAULT]
_heightRelativeTo:ContainerSizeRelativeTo = 'page-content-area';
_position:Array<number|number>; // x,y in relative coords [0-1] of page-content-area
_pivot:Array<number|number>; // x,y in percentage [0-1] of [containerWidth,containerHeight] - leftbottom = [0,0]
_frame:Frame;
_index:number; // ordering z-index
_caption:string;
_contentAlign:ContainerAlignment;
_content:any; // TODO: raw content (like Svg for View, source for Image etc)
_zoomLevel:ScaleInput;
_zoomRelativeTo:ZoomRelativeTo;
constructor(name:string)
{
this.name = name;
}
_setDefaults()
{
this.width(this.WIDTH_DEFAULT);
this.height(this.HEIGHT_DEFAULT);
this.pivot(this.PIVOT_DEFAULT);
this.position(this.POSITION_DEFAULT);
}
on(page:Page):Container
{
this._page = page;
this._setDefaults();
page.add(this); // Add to Page
return this;
}
/** Set width of this Container. Either in percentage ([0-1]) of (page or content) width or in % or units */
width(n:WidthHeightInput)
{
if(!isWidthHeightInput(n)){ throw new Error(`Container::height: Invalid input "${n}": Use a number, number with units ("30mm") or string like "40%"!`)};
[this._width, this._widthRelativeTo] = this._page._doc._resolveWidthHeightInput(n, this._page, 'width');
console.info(`Container::width(): Set container width to ${this._width}`);
}
/** Set height of this Container. Either in percentage ([0-1]) of width or in % or units */
height(n:WidthHeightInput)
{
if(!isWidthHeightInput(n)){ throw new Error(`Container::height: Invalid input "${n}": Use a number, number with units ("30mm") or string like "40%"!`)};
[this._height, this._heightRelativeTo] = this._page._doc._resolveWidthHeightInput(n, this._page, 'height');
console.info(`Container::width(): Set container height to :${this._height}`);
}
/** Set position with a ContainerAlignment ('top', 'topright') or percentage of width and height [x,y] */
position(p:Position):Container
{
if(!isPosition(p)){ throw new Error(`Container::pivot: Invalid input "${p}": Use [widthPerc,heightPerc] or ContainerAlignment ('center','topleft'etc)`)};
if(isContainerAlignment(p))
{
this._position = this.ALIGNMENT_TO_WPERC_HPERC[p];
return this;
}
else {
this._position = p as Array<number|number>;
return this;
}
}
/** Set pivot with a ContainerAlignment ('top', 'topright') or percentage of width and height [x,y] */
// TODO: enable 20%, 2cm from origin
pivot(p:Position):Container
{
if(!isPosition(p)){ throw new Error(`Container::position(): Invalid input "${p}": Use [widthPerc,heightPerc] or ContainerAlignment ('center','topleft'etc)`)};
if(isContainerAlignment(p))
{
this._pivot = this.ALIGNMENT_TO_WPERC_HPERC[p];
return this;
}
else {
this._pivot = p as Array<number|number>;
return this;
}
}
/** Set zoom level (which is relative to view container size) */
zoom(factor:number)
{
if(typeof factor !== 'number'){ throw new Error(`Container::zoom(): Invalid input "${factor}" for zoom relative to container: Use a number like 2 (zoom in 2x) or 1/2 (zoom out 2x)`)};
this._zoomLevel = factor as ScaleInput;
this._zoomRelativeTo = 'container';
}
/** Set zoom level relative to world size of Shapes */
scale(factor:ScaleInput)
{
if(!isScaleInput){ throw new Error(`Container::scale(): Invalid input "${factor}" to set zoom relative to world size: Use 'auto' (for automatic picking scale) or a number like 2 (2:1) or 1/10 (1:10)`)};
this._zoomLevel = factor;
this._zoomRelativeTo = 'world';
}
//// OUTPUT ////
/** Transform from relative width (to page or page-content-area) to absolute (in DocUnits) */
_calculateAbsWidth():number
{
const relToSize = (this._widthRelativeTo === 'page-content-area') ?
this._page._width - 2*this._page._width*this._page._padding[0] : this._page._width
return this._width * relToSize;
}
/** Transform from relative height (to page or page-content-area) to absolute (in DocUnits) */
_calculateAbsHeight():number
{
const relToSize = (this._heightRelativeTo === 'page-content-area') ?
this._page._height - 2*this._page._height*this._page._padding[1] : this._page._height
return this._height * relToSize;
}
/** Output raw Container data */
// NOTE: We use toData from the subclasses of Container (they use this function)
_toContainerData():ContainerData
{
return {
_entity: 'container',
name: this.name,
parent: this._parent?.name,
type: this._type,
width: this._width, // relative to page-content-area or page
widthRelativeTo: this._widthRelativeTo,
widthAbs: this._calculateAbsWidth(),
height: this._height, // relative to page-content-area or page
heightRelativeTo: this._widthRelativeTo,
heightAbs: this._calculateAbsHeight(), // in doc units
position: this._position,
pivot: this._pivot,
frame: this._frame,
index: this._index,
caption: this._caption,
contentAlign: this._contentAlign,
content: null,
zoomLevel: this._zoomLevel || 1,
zoomRelativeTo: this._zoomRelativeTo || 'container',
docUnits: this._page._units, // needed to scale the content
modelUnits: this._page._units, // needed to scale the content
}
}
toData():ContainerData
{
// will be overriden by subclasses
return null;
}
//// UTILS ////
}