Overview

Canvas allows users to interact with the CadX technology in the browser. The objectives of the API are:

Canvas HTML Component

Implements an HTML component where documents can be rendered and manipulated by the user.

Canvas 3D HTML Component

Provides a 3D model viewer for real time edition of designs.

Canvas Scripting

Provide an imperative view of CDL that support both simple and complext manipulation of documents.

var figures = canvas.selectedFigures();
canvas.group(figures);
canvas.commit$();

Canvas API Core services

Expose CadX Core services in packed an easy to use functions that returns concrete JavaScript objects (Document, Figure, Brush). Users should be able to load a document, change it and save it back in the server with ease:

Cx.Document.load$(id) .then(function(doc){
// Modify doc...
doc.save();
});

Setup

Canvas depends on CoreJS and it uses the Cx.js loading scheme. Given that CoreJS path is [Core] and Canvas path is [Canvas], the minimun setup looks like:

<html>
<head>
<script type="text/javascript" src="[Core]/Cx-include.js"></script>
<script type="text/javascript" src="[Canvas]/Canvas-include.js"></script>
<script type="text/javascript">
Cx.js( Cx.Core({ base: '[Core]' })
, Cx.Canvas({ base: '[Canvas]' }));
Cx.ready( function(){
var canvas = Cx.Canvas({ renderTo: 'CadXCanvas' });
});
</script>
</head>
<body>
<div id="CadXCanvas" style="width:500px,height:500px"></div>
</body>
</html>

Both Core and Canvas are versioned libraries:

CoreJS needs a running CadXWorX server. The Edge version of the server is deployed at http://cadxfarm1.cloudapp.net/master-edge.
You can tell Core the path of the server using:

Cx.Core.setServer('cadxfarm1.cloudapp.net/master-edge');

As with any other config option, you can alternatevily define it using an URL switch:

http://canvas.app.com?cadx.server=cadxfarm1.cloudapp.net/master-edge

Check the Configuration reference for a complete list of options. While developping your app is advisable you always include &cadx.v=&cadx.debug=true in your app URL. This will expose the Canvas debug tools, read the Debugging page for more information.

A simple Demo app that only adds a Canvas in the center of the page can be accessed at:
http://cadxcdn.blob.core.windows.net/canvas-edge/Demo/index.html?cadx.server=devapp.poweredbycadworx.com&sid2=4ad899d5-89ca-4239-9d95-79b20d844ac8&cadx.v=&cadx.debug=true

Cdl

Document, Pages, Layers, Figures

<Cdl>
<Document Name="String" XType="String" ...>
<Background Id="DocumentId"><Brush/></Background>
<Pages>
<Page Name="String" XType="String" ...>
<Layers>
<Layer Name="String" XType="String" ...>
<Figures>
<TextBlock/>
<Shape/>
<Raster/>
<Group/>
<ProxyGroup/>
...

Figure

There are 5 figure types: <TextBlock>, <Shape>, <Raster>, <Group> and <ProxyGroup>.
Common properties of all figure types:

<Figure Name="String" XType="String"
Locked="Boolean" Hidden="Boolean" ...>
<Brush/>
<Pen/>
<PreTransform Matrix22="1,0,0,1"/>
<Anchor X="mm" Y="mm" -or- RelX="Number" RelY="Number"/>
<Transform Matrix22="1,0,0,1"/>
<Pin X="mm" Y="mm"/>
<PowerClipContents>
<PowerClipContent Id="String">
<Figures>...</Figures>
</PowerClipContent>
...
</PowerClipContents>
<Figure>

Text

<TextBlock LineDirection="Angle" CharDirection="Angle"
ExtraCharSpacing="Magnitude"
ExtraWordSpacing="Magnitude"
ExtraLineSpacing="Magnitude"
ColumnSpacing="Magnitude"
ColumnCount="Integer" LinesPerColumn="Integer"
LinesAlign="Left|Center|Right"
PowerClipId="String"
VerticalStyle="String" ... />
<Line>String</Line>
<Font Size="Number" AutoSize="Boolean"
Guid="String" Name="String"
FamilyName="String" FullName="String" PostscriptName="String"
Weight="Number" IsBold="Boolean"
IsItalic="Boolean" IsStrikeout="Boolean" IsUnderline="Boolean"/>
<TextPart TextRange="" ...same as TextBlock... >
<Font/>
</TextPart>
...
</TextBlock>

Shape

<Shape ...>
<!-- Unstructured polyregion -->
<Polyregion FillMode="Alternate|Winding" PowerClipId="String"
RenderFrame="0 0, 100 0, 100 100, 0 100"
RenderMatrix="1,0,0,1">
<Region Type="Bezier3" IsClosed="Boolean" Points="0 0,10 0,15 5|20 10,20|..."/>
<Region Type="Polyline" IsClosed="Boolean" Points="0 0,10 0,10 10,15 10..."/>
...
</Polyregion>
<!-- Boolean polyregion -->
<Polyregion FillMode="CadX" ...>
<Region ...>
<Hole Type="Bezier3" IsClosed="True" Points="..."/>
<Hole Type="Polyline" IsClosed="True" Points="..."/>
...
</Region>
...
</Polyregion>
...
</Shape>

Raster

<Raster ImageLink="Id" Width="px" Height="px" DPI="Number">
<MonochromeColors><Color/><Color/></MonochromeColors>
</Raster>

Group

<Group ...>
<Figures>...</Figures>
</Group>

ProxyGroup

<ProxyGroup Link="Id" ...>
<Colors>
<Color/>
<Color/>
...
</Colors>
...
</ProxyGroup>

Style

Solid Brush

<Brush>
<Color/>
</Brush>

Texture Brush

<TextureBrush ImageLink="Id"
TextureOffset="0.5 0.5" TargetOffset="0.5 0.5"
Matrix22="1,0,0,1"
TransformMode="Fixed|Clamp|RotateOnly|Full|StretchX|StretchY"
ClippingRect="lx ly hx hy">
<MonochromeColors><Color/><Color/></MonochromeColors>
</TextureBrush>

Linear Gradient Brush

<LinearGradientBrush Positions="[Points]"
From="Number" To="Number"
GammaCorrection="Boolean">
<Colors>
<Color/>
<Color/>
...
</Colors>
</LinearGradientBrush>

Radial Gradient Brush

<RadialGradientBrush Center="Point" Positions="[Numbers]">
<Colors>
<Color/>
<Color/>
...
</Colors>
</RadialGradientBrush>

Pen

<Pen Width="Number"
DashStyle="Solid|Dash|Dot|DashDot|DashDotDot"
StartCap="Flat|Square|Round|Triangle" EndCap="..."
DashCap="Flat|Round|Triangle"
LineJoin="Miter|Bevel|Round|MiterClipped|AdaptiveMiter"
MiterLimit="Number"
BehindFill="Boolean">
<Color/>
</Pen>

Color

<Color Alpha="0...1" RGB="RRGGBB" CMYK="CCMMYYKK"
Name="String" Tag="String" Label="String"
ShowThrough="Boolean"/>

Processes

Capital

<Capital ScaleX="Number" ScaleY="Number"
Pattern="First|Last|First Last"
HAlignment : "Left|Center|Right"
VAlignment : "Baseline|Top|Middle|Bottom"/>

BookEnd

<BookEnd HeadSX="Number" HeadSY="Number"
TailSX="Number" TailSY="Number"
HAlignment : "Left|Center|Right"
VAlignment : "Baseline|Top|Middle|Bottom"/>

Split

<Split Overlap="Number"
Pos="Number"
Turn="Number"
Gap="Number"/>

Border

<Border DX="Number" DY="Number"
Gap="Number"
Separate="Number"/>

Contour

<Contour Offset="Number"
Steps="Integer"
Inside="Boolean" Outside="Boolean"
IncludeHoles="Boolean"
Combine="Boolean"
Chamfer_D="Number" Chamfer_L="Number">
<Brush/>
<Pen/>
</Contour>

PenContour

<PenContour>
<Pens>
<Pen/>
<Pen/>
...
</Pens>
</PenContour>

Shadow

<Shadow OffsetX="Number" OffsetY="Number"
Gap="Number"
Simplify="Boolean">
<Brush/>
<Pen/>
</Shadow>

RasterShadow

<RasterShadow OffsetX="Number" OffsetY="Number">
<Pen/>
<Gap>
<Pen/>
</Gap>
</RasterShadow>

Effect3d

<Effect3d BorderIntensity="Number" BorderSize="Number"
ContourOffset="Number"
LightGap="Number" LightIntensity="Number"
LightSize="Number" LightSource="Up|Right|Down|Left"
ShadowIntensity="Number" ShadowSizePercentage="Number"
SlopeIntensity="Number" SlopeSize="Number"/>

Extrude3D

<Extrude3D Depth="Number" VanishingPoint="Point"
RotationX="Angle" RotationY="Angle" RotationZ="Angle">
<Brush/>
<Pen/>
</Extrude3D>

VerticalArch

<VerticalArch Height="Number" Width="Number" From="Angle" To="Angle"/>

Perspective

<Perspective HPoint="Point" VPoint="Point"/>

FitEnvelope

<FitEnvelope Mode="Freeform|Mixed|Uniform|Boxed"
HPos="Left|Center|Right|Stretched|Expanded"
VPos="Top|Center|Bottom|Stretched|Expanded"
HighResolution="Boolean"
KeepLines="Boolean"
AddBearingX="Boolean">
<Envelope>
<Curve Type="Bezier3" IsClosed="False" Points="..."/>
<Curve Type="Bezier3" IsClosed="False" Points="..."/>
<Curve Type="Bezier3" IsClosed="False" Points="..."/>
<Curve Type="Bezier3" IsClosed="False" Points="..."/>
</Envelope>
</FitEnvelope>

FitEllipse

<FitEllipse Height="Number" Width="Number"
Rotation="Angle"
Placement="Number"
Mirror="Boolean"
Inside="Boolean"
DontRotate="Boolean"
Fit="None|Scaled|Contained|Stretched|Expanded"/>

FitOnPath

<FitOnPath VPos="Above|Over|Below"
HPos="Fixed_Left|Fixed_CenterTop|Fixed_CenterBottom|
Fixed_Right|Stretched|Scaled|Expanded"
Offset="Number" DistanceFromPath="Number"
PlaceOnOtherSide="Boolean"
Fwd="Boolean"
DontRotate="Boolean"
Overhang="Boolean">
<Path Type="Bezier3" IsClosed="Boolean" Points="..."/>
</FitOnPath>

Distress

<Distress ScratchesLevel="Number"
RustLevel="Number"
FoldsLevel="Number"
OnOffThreshold="Number"/>

Primitives

Point

The documentation assumes the following variable is defined, it is recommended to use the same convention on user code:

var Px = Cx.Point;
var p = Px(x,y);
p.x p.y
p.set(x,y); // Modify inplace
p.set(point); // p := point
p.clone(); // return Px(p)
p.isEqual(point); // return p == point
p.isNear(point,eps); // return p ~= point
p.isZero(); // return x == 0 && y == 0
p.add(point); // p += point
p.substract(point); // p -= point
p.scalarMultiply(alpha); // p *= alpha
p.sm(alpha); // Shortcut for scalarMultiply
p.translate(offset); // p += offset, same as add
p.translate(dx,dy); // x += dx, y += dy
p.translated(offset);
p.translated(dx,dy);
p.transform(transform); // p := transform.apply(p)
p.transformed(transform); // return transform.apply(p)
p.toSVG(); // return 'x y'
p.toXmlString(); // return 'x y'
p.textualize(); // return 'x y'
p.toCore(); // return { X: x, Y: y }
p.norm2(); // return x2 + y2
p.norm(); // return sqrt( norm2 )
p.normalize(); // p := p.normalized()
p.normalized(); // return p / norm
p.perpendicular(); // return orthogonal vector
p.confined(bounds);
p.confined(lx,ly,hx,hy);
p.confine(bounds);
p.confine(lx,ly,hx,hy);
p.angle(); // return angle with respect to the x axis
p.angle_rad(); // return angle in radians
Px.areEqual(pa,pb); // return pa == pb
Px.fromXmlString(cdlPoint); // 'x y' => Px
Px.fromCore(pointModel); // {X,Y} => Px
Px.distance(pa,pb); // return euclidean distance between points
Px.distance2(pa,pb); // return squared euclidean distance
Px.minus(p); // return -p
Px.add(pa,pb); // return pa + pb
Px.substract(pa,pb); // return pa - pb
Px.scalarMultiply(p,alpha); // return p * alpha
Px.sm(p,alpha); // Shortcut for Px.scalarMultiply
Px.dotProduct(pa,pb); // return pa * pb
Px.crossProductZ(pa,pb); // return pa ^ pb
Px.interpolate(pa,pb,t); // return pa * t + pb * (1-t)
Px.isInsidePolygon(point,polygon);

Matrix

The documentation assumes the following variable is defined, it is recommended to use the same convention on user code:

var Mx = Cx.Matrix;
var m = Mx(s00,s01,s10,s11, dx,dy);
m.s00 m.s01 m.dx
m.s10 m.s11 m.dy
m.apply(point); // return m * point
m.apply(x,y); // return m * Px(x,y)
m.clone(); // return Mx(m);
m.isEqual(matrix); // return m == matrix
m.isNear(matrix,eps); // return m ~= matrix
m.isIdentity();
m.isAlmostIsotropic(); // Only rotation and isometric scaling
m.appended(matrix); // return m * matrix
m.determinant(); // return s00 * s11 - s01 * s10
m.scaleFactor(); // return sqrt(abs(determinant))
m.angle(); // return rotation angle
m.invert();
m.inverted();
m.toCssString(unit); // return 'matrix(s00,s01,s10,s11,dx unit,-dy unit)'
m.toMatrix22String(); // return 's00 s01 s10 s11'
m.toMatrixString(); // return 's00 s01 s10 s11 dx dy'
m.textualize(); // return 's00 s01 s10 s11 dx dy'
m.toCore(); // return { S00, S01, S10, S11, DX, DY }
// Only for matrix22
Mx.add(m1,m2); // return m1 + m2
Mx.substract(m1,m2); // return m1 - m2
Mx.scalarMultiply(matrix,alpha); // return matrix * alpha
Mx.sm(matrix,alpha); // scalarMultiply shortcut
Mx.multiply(ml,mr); // return ml * mr
Mx.concat(m1,m2,m3,...); // return m1 * m2 * m3 * ...
Mx.invert(matrix); // return matrix ^ -1
Mx.translate(offset); // return translation matrix
Mx.translate(dx, dy); // return translation matrix
Mx.scale(sx, sy, deg); // return scaling matrix
Mx.scaleFrom(point, sx, sy, deg); // return scaling matrix
Mx.rotate(deg); // return rotation matrix
Mx.rotateAround(point, deg); // retrun rotation matrix
Mx.shear(sx, sy, deg); // return shear matrix
Mx.shearAround(point, sx, sy, deg); // return shear matrix
Mx.transformAround(point, matrix); // return general matrix
Mx.stringToMatrix22(string); // 's00 s01 s10 s11' => Matrix
Mx.fromCore(m); return { S00, S01, S10, S11, DX, DY } => Matrix

Bounds

var b = Cx.Bounds(lx, ly, hx, hy);
b.lx b.ly b.hx b.hy
b.clone();
b.isEqual(bounds); // return b == bounds
b.lx_ly();
b.lx_hy();
b.hx_ly();
b.hx_hy();
b.center();
b.area();
b.width();
b.height();
b.translated(offset);
b.translated(x,y);
b.translate(offset);
b.translate(x,y);
b.scaled(sx,sy);
b.scale(sx,sy);
b.inflated(dw,dh);
b.inflate(dw,dh);
b.contains(point); return ( point inside b )
b.contains(x,y);
b.interpolate(x,y); // return Px( lx + x width, ly + y height )
b.deinterpolate(point); // return {x,y} such that point.x == lx + x width
// point.y == ly + y height
b.toXmlString();
b.textualize();
Cx.Bounds.fromBox(box); // { x, y, width, height } => Cx.Bounds
Cx.Bounds.fromXmlString(s); // 'lx ly hx hy' => Cx.Bounds
Cx.Bounds.fromCore(m); // { LX, LY, HX, HY } => Cx.Bounds
Cx.Bounds.ofPoints(points); // return points bounds
Cx.Bounds.ofPointsAfter(points,transform);
Cx.Bounds.ofThings(things); // return things bounds
Cx.Bounds.ofThingsAfter(things,transform);
Cx.Bounds.overlapPercentage(ba, bb)
Cx.Bounds.horizontalOverlapPercentage(ba, bb)
Cx.Bounds.areOverlapping(ba,bb)
Cx.Bounds.intersection(ba, bb)
Cx.Bounds.union(boundsArray)

Frame

var f = Cx.Frame(p0,p1,p2,p3);
f.p0 f.p1 f.p2 f.p3
f.clone();
f.isEqual(frame); // return f == frame
f.bounds();
f.center();
f.width(); // return distance(p0,p1)
f.height(); // return distance(p0,p3)
f.width2(); // return distance2(p0,p1)
f.height2(); // return distance2(p0,p3)
f.minSideLength2();
f.translate(dx,dy);
f.translated(dx,dy);
f.transform(t);
f.transformed(t);
f.interpolate(ax,ay);
f.toXmlString();
f.contains(point);
f.extend(olx,oly,ohx,ohy);
f.extended(olx,oly,ohx,ohy);
f.angle(); // p0 p1 angle
Cx.Frame.fromRect(x,y,width,height);
Cx.Frame.fromBounds(bounds)
Cx.Frame.around(center,width,height);
Cx.Frame.fromXmlString(s); // 'p0x p0y ... ' => Cx.Frame
Cx.Frame.areOverlapping(fa,fb);
Cx.Frame.overlapPercentage(fa,fb);

Objects

Canvas API exposes an imperative view of CDL (CadWorX Declarative Language), format used through all our software stack. It is designed to serve both simple (replacing a figure in a template) and complex manipulation of CDL document and templates.

The API follows JavaScipt semantics and naming conventions as much as possible, and fills the gaps borrowing the style of other popular frameworks like JQuery and Lo-dash, libraries that are used internally and encourage to interact with the API.

CDL tag names are CamelCase. For the most part, they are translated to Canvas using the same CamelCase tag name for type names and backCase for properties.

// <Contour OffsetX="10"></Contour>
var c = Cx.Contour({ offsetX: 10 });

Creation of elements

CDL elements are translated to typed objects in Canvas:

CDL Element Canvas Object
<FigureType> Cx.FigureType
<BrushType> Cx.BrushType
<PenType> Cx.PenType
<ProcessType> Cx.ProcessType
<Color> Cx.Color
Matrix22=" " Cx.Matrix ( Mx in the docs )
<Point X=" " Y=" "> Cx.Point ( Px in the docs )

Objects are initialized using JSON notation:

var contour = Cx.Contour({ offset: 10
, brush: { color: { rgb: 'FF0000', alpha: 0.5 } } });

You can mix JSON and Canvas objects if you already have the objects available:

var contour = Cx.Contour({ offset: 10
, brush: figure.brush() });

Properties

Following JQuery properties semantics, CIL properties exposes both get and set functionality directly using the property name to define a public function (with or with out parameters).

// Getter
var ox = shadow.offsetX();
// Setter
shadow.offsetX( ox+10 );

Setters return the modified object and can be chained to update several properties in a single line:

// Setters chaining
shadow.offsetX(10)
.offsetY(10)
.gap(3);

The values returned from properties are typed. You will get a number, a string or a brush when you query the for them. Property setters expect the same courtesy from the user, and passing a string when a number is required is undefined behavior.

Array properties

Working with lists of items is very common when dealing with CDL documents. A document is a list of figures, a figure has a list of processes, a linear gradient brush has a list of colors, Canvas has a list of selected figures. These lists are also exposed as normal properties:

// Getter
var figures = layer.figures();
// Setter
layer.figures( [text, shape, raster] );

The setter in this case will directly replace all figures in the document by the new given figure list.

Array properties can also be accessed using indexing:

// Get one figure
var figure = layer.figures(2);
// Replace one figure
layer.figures( 2, raster );

Array properties getters returns an extended version of a JavaScript Array. These arrays tries to mimic as much as possible the standard Array interface. All the functions in the standard are available. They work in the same way as normal arrays:

var figures = layer.figures();
for(var k=0, size=figures.length; k<size; ++k){
var figure = figures[k];
figure.translate(offset);
}

The new JavaScript standard iteration functions (and the extensions provided by lo-dash) are great tools for list handling, reading Underscore.js documentation is heavily recommended before using Canvas. The algorithm above can be writen as:

_.invoke( layer.figures(), 'translate', offset );

Cx Arrays extends the standard set of mutation functions to help in our domain. For example, following the example above, the user can remove the small figures using:

figures.remove(smallFigures);

The new mutation functions are:

Function Definition
.clear() Remove all elements
.add(a) Add elements
.set(a) Clear and add elements
.remove(e) Remove given elements
.erase(i,n) Remove a range (same as splice(i,n?n:1))
.insert(i,a) Insert element at a given position
.replace(e,a) Replace elements
.replaceAt(i,v) Replace element at given index
.moveForward(v,n) Relocates elements forward
.moveBackward(v,n) Relocates elements backward

For all functions, the parameters specs are:

  • a value or array of values to be added
  • e value or array of values to be removed
  • v value or array of values to be relocated
  • i array index
  • n count

Functions that remove values return these values so the user can use them or dispose them if needed.

Canvas

var canvas = Cx.Canvas({ renderTo: domId });

Properties

Properties Type Def Definition
zoom Number 1 View port transform zoom
center Point (0,0) View port transform center
wireframe Boolean false Show outline structure of figures
document Document Empty Document shown in the Canvas
selectedFigures Figures [] Current selection
background Document Empty Decoration background for the Canvas
backgroundColor Color White Background color, use null for transparency
maskColor Color Gray Mask color when using MaskPen outlines
maskAlpha Number 0.8 Mask alpha
locked Boolean false If true, tools and actions are not allowed to modify the document
showLoadingAnimation Boolean true Show loading feedback while waiting for the server
toolbar Toolbar null HUD Toolbar definition
hudClipboard Boolean false Show Clipboard button on the HUD
autoSelection Boolean true Newly added figures are auto selected

Events

Events Definition
'ready' Canvas is ready
'resize' Canvas size changed
'zoomupdated' zoom or center updated

Viewport management

canvas.zoomTo(zoom,center);
canvas.zoomBy(delta); // zoom += delta
canvas.zoomIn(factor); // zoom *= factor
canvas.zoomOut(factor); // zoom /= factor
canvas.zoomAround(bounds,margin);
canvas.zoomToFit$(figures,margin);
canvas.zoomToFitPages$(margin);
canvas.center(bounds);
canvas.center$(figures);
canvas.bounds();

Tools

canvas.tool(); // return current tool
canvas.tool(tool); // stack a new tool
canvas.popTool(); // commit current tool

Document forwarding interface

To make it easier to work with the canvas document, the following forwarding functions are provided:

canvas.pages(i,a);
canvas.activeLayerFigures(i,a);
canvas.activePageFigures(i,a);
canvas.layers(i,a);
canvas.figures(i,a);
canvas.allFigures();
canvas.commit$();
canvas.revert$();
canvas.update$();
canvas.add(figures);
canvas.remove(figures);
canvas.replace(old,new);
canvas.backOne(figures);
canvas.toBack(figures);
canvas.forwardOne(figures);
canvas.toFront(figures);
canvas.saveCopy$(attrs);
canvas.save$(attrs);
canvas.addClipart$(clipart);
canvas.replaceClipart$(old,clipart);
canvas.autoCreateMultiPartText$(figures);
canvas.breakMultiPartText(text);
canvas.group(figures);
canvas.ungroup(figures);
canvas.extractPowerClipContents(figure);
canvas.addPowerClipContents(figure,content);
canvas.reshape$(figures,action);

Selection helpers

canvas.selectAll(); // Shortcut for canvas.selectedFigures( doc.activeLayerFigures() )
canvas.clearSelection(); // Shortcut for canvas.selectedFigures().clear()
canvas.selectNext(); // Select next figure in z-order
canvas.keepSelection(); // Auto selection will not kick in for current transaction

Misc

// Returns a copy of the current zoom, offset, wireframe that can be stored to set back later
var s = canvas.state();
// Later...
canvas.state(s);
canvas.fill(context);
canvas.updateSize();
canvas.dispose();
canvas.render$();
canvas.contextFigures(i,a); // selection ? selectedFigures(i,a)
// : doc.figures(i,a)
canvas.action(id,config);
canvas.showPagesSelector$();
canvas.figureAtPoint( docPoint );

Tools aware history

if( canvas.canUndo() ) canvas.undo();
if( canvas.canRedo() ) canvas.redo();

Figure Annotations

canvas.annotate( figure, { message: 'Message', pen: ... } );
canvas.annotate( figures, { grouped: true, message: ... } );
canvas.annotate( function(figure){ }, { message: function(figure){}, ... });
canvas.annotate( function(figure){ }, function(figure){} );
canvas.annotate( function(figure){ }, function(figure){} );
canvas.unannotate( annotation );
canvas.clearAnnotations();

Conditions

Canvas conditions are part of the figures conditions API. Read the Conditions tutorial for more information.

canvas.condition(name,function(figure){ return boolean; });
canvas.arrayCondition(name,function(figures){ return boolean; });
canvas.is(figure,condition...);
canvas.is$(figure,conditions...);
canvas.are(figures,conditions...);
canvas.are$(figures,conditions...);
canvas.only(figures,conditions...);
canvas.only$(figures,conditions...);
canvas.saveConditions();
canvas.restoreConditions();

Canvas background

The canvas background is used to show decorations or app specific guides.

var backgrondDocument = canvas.background();
// ... modify and commit$ like a normal document

The background document is independent from the document the Canvas is displaying. You can have a document (the design you will transfer to a shirt) and display it with different kind of backgrounds: a realistic t-shirt for a final preview, a simplified version that is just a bounding box to aid in edition mode, etc.

If you want a background that will be persisted with the document, then use a background layer in your documents. The Canvas background is only for the apps to be able to add things like a page box for reference.

3D Applications

Simple 3D Application

For applications that needs to preview a product and offer basic modifications in 3D, a light weight version of the 3D machinery can be used. The model view

Cx3D.createPreview(domId,docId);

This function will give you back a Cx3D.Preview object that derives from Cx3D.ModelView and holds a Cx3D.ProductionTexture internally to make things easier on the dev side. This kind of application doesn’t use a Cx.Canvas at all, improving the performance in case many previews are used on the same page.

A demo can be found here.

<!DOCTYPE html>
<html>
<head> <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
<title>Cx Canvas</title>
<script type="text/javascript" src="http://cadxcdn.blob.core.windows.net/core-edge/Cx-include.js"></script>
<script type="text/javascript" src="http://cadxcdn.blob.core.windows.net/canvas-edge/Canvas-include.js"></script>
<script type="text/javascript">
Cx.Config.three = true;
Cx.js( Cx.Core( { base: 'http://cadxcdn.blob.core.windows.net/core-edge' } ),
Cx.Canvas( { base: 'http://cadxcdn.blob.core.windows.net/canvas-edge' } ) );
</script>
</head>
<body>
<div id="preview" style="width:500px;height:500px;"></div>
<script type="text/javascript">
Cx.ready(function(){
Cx3D.createPreview('preview','8c4642b0-4669-4da9-a22c-b1e058f7ec2f');
});
</script>
</body>
</html>

Document Filter

If the app wants to show a modified version of the template, to avoid flickering, the document needs to be modified before any rendering. This is done using a document filter function:

Cx3D.createPreview( domId, docId, function(doc){
// modify what you want, no need to commit anything
// if you need any metrics, return the promises so we can wait for them
});

For example, modifying the first Text in the document is done with:

var preview = Cx3D.createPreview('preview','8c4642b0-4669-4da9-a22c-b1e058f7ec2f',function(doc){
var figure = Cx.findByType( doc.allPagesFigures(), Cx.Text );
if( figure )
figure.text('New text');
});

Modifying the document with user input

If the apps wants to show the original template, and later allow the user to change the text.

preview.ready$() .then(function(doc){
// Ok, document is loaded, you can enable your text box or buttons
})

Change actions over the document are written as usual:

// this shouldn't be called if the preview is not ready...
onTextChanged: function(value){
var doc = preview.document();
var figure = Cx.findByType( doc.allPagesFigures(), Cx.Text );
if( figure ){
figure.text(value);
doc.commit$();
}
}

Full Canvas Application

Later…

Canvas 3D

Document 3D, Model 3D and Model Data 3D.

Document 3D

A Document 3D extend Cx.Document with 3D helpers. Given an id in the server of a Document 3D the load function is the same as with normal documents:

Cx.Document.load$(id) .then(function(doc){
// doc is of type Cx3D.Document
Properties Type Def Definition
model3D String null Server Id of associated Model 3D
modelData3D String null Server Id of associated Model Data 3D

The 3D document overwrites the document add function. It can trigger an active layer change so islands don’t get mixed with the design.

doc.add(figures);

Helper methods to work with islands and design layers

doc.islandsLayer(); // return active page islands layer
doc.designLayers(); // return active page design layers
doc.islandsLayer(page); // return page islands layer
doc.designLayers(page); // return page design layers
doc.isTextureIsland(figure);

Texture islands with the same name across all pages should share the same brush to keep the system consistent. Use the following function when changing the brush of a texture island:

doc.textureIslandBrush(figure,brush);

One possible flow for a multi size 3D application is to ask the user to design one size to create a template that will be copied over all other sizes. The following function implements a scaled or non scaled copy, it is expected that the arrangements in both documents are similar.

doc.copyDesignTo$(doc,{scaled:false}) .then( ...

Model 3D

Given a Document 3D, the associated model can be accesed with:

doc.loadModel3D$() .then(function(model) { ...

In case of having the server id of the Model, or the Xml representation:

Cx3D.Model.load$(id) .then(function(model){ ...
var model = Cx3D.Model.fromXml(xml);
var model = Cx3D.Model.fromXmlString(s);
Properties Type Def Definition
modelData3D String null Server Id of associated Model Data 3D

An empty working space document associated with this model can be created with:

model.createDocument3D$() .then(function(doc3D){ ...

A new arrangement containing all islands in their original position can be create with:

var page = model.createCustomPage(name);

Model Data 3D

Given a Model 3D, the associated model data can be loaded with:

model.loadModelData3D$() .then(function(modelData){ ...

Or using its server id:

Cx3D.ModelData.load$(id) .then(function(modelData){ ...
Properties Type Def Definition
data json null 3D polygonal data
id String null Server Id

Production Texture

var texture = Cx3D.ProductionTexture({ renderTo: domId });
Properties Type Def Definition
document Document 3D null Working space document
model Model 3D null Model 3D
dpi Number 2 * 25.4 Resolution
showSelectedFigures Boolean true Show selection in 3D
texture.watch(canvas);
texture.dispose();
texture.updateSize();

Model View

var mv = Cx3D.ModelView({ renderTo: domId });
Properties Type Def Definition
backgroundColor Color Gray Background color, null for transparency
autoRotate Boolean false Auto rotate model
loadingAnimationMessage String 'Loading 3D Model' Message
mv.modelData(modelData);
mv.watch(texture);
mv.dispose();
mv.image();
mv.updateSize();
mv.zoomToFront();
mv.zoomToBack();
mv.zoomToRight();
mv.zoomToLeft();
mv.zoomIn(scale);
mv.zoomOut(scale);
mv.rotateCameraRight(angle);
mv.rotateCameraLeft(angle);
mv.rotateCameraDown(angle);
mv.rotateCameraUp(angle);
mv.rotateModelRight(angle);
mv.rotateModelLeft(angle);
mv.rotateModelDown(angle);
mv.rotateModelUp(angle);

Thumbail image

Cx3D.thumbnailImage$(config);

3D Preview

var preview = Cx3D.createPreview(domId,docId);

Document

Cx.Document

Properties

Properties Type Def Definition
id String null Id of the document in the Server
docType String 'Doc' Server side document type
xType String null Document type
undoLimit Integer 10 Maximun number of history points kept
name String null Name stored in the Server
notes String null Notes stored in the Server
owned Boolean true The current user owns this document
archived Boolean false The document is archived in the Server
tags Tag Array [] Core tags
metadata Metadata None Extra app data

Events

Events Definition
'saved' Fired after the document is saved in the Server
'lock' Fired when a transaction starts
'unlock' Document is able to process next transaction
'commitstarted' Commit started, hook to assure things like showThrough color
'commit' Transaction finished succesfully
'revert' Transaction failed, state reverted to previous history point
'undo' Fired after an undo operation
'redo' Fired after a redo operation
'pageschanged' Pages array changed
'activepagechanged' Active Page changed
'layerschanged' Layers array changed
'activelayerchanged' Active layer changed

Creation

var doc = Cx.Document();

Pages

doc.pages(i,a);
doc.activePage();
doc.activePageIndex();
doc.releasePages(pages);

Layers

doc.layers(i,a);
doc.activeLayer();
doc.activeLayerIndex();
doc.releaseLayers(layers);

Figures

doc.figures(i,a);
doc.allFigures();

The document offers helper functions to directly modify the figures in the active layer. The doc.group(figures) function will remove the figures from the document, and add them grouped.

doc.backOne(figures);
doc.toBack(figures);
doc.forwardOne(figures);
doc.toFront(figures);
doc.autoCreateMultiPartText$(figures);
doc.breakMultiPartText(text);
doc.group(figures);
doc.ungroup(figures);
doc.extractPowerClipContents(figure);
doc.addPowerClipContents(figure,content);
doc.reshape$(figures,action);

Transactions

doc.lock();
doc.unlock();
doc.commit$();
doc.revert$();
doc.update$();

Utilities

doc.background();
doc.clone();
doc.toXmlString();

Loading and Saving

Cx.Document.load$(id) .then(function(doc){ ...
Cx.Pages.load$(id) .then(function(pages){ ...
Cx.Layers.load$(id) .then(function(layers){ ...
Cx.Document.fromXmlString(string);
Cx.Document.fromXml(node);
doc.saveCopy$();
doc.save$();
doc.isSaved();

History

Each document keeps track of its history up to undoLimit states. History is used both as a way to let users undo and redo their work, and to allow transactions to be properly reverted.

if( doc.canUndo() )
doc.undo();
if( doc.canRedo() )
doc.redo();

When implementing undo and redo actions for your app, use the canvas.undo() machinery that uses tools history instead of plain document history. Read more about Canvas and tools history in the Canvas reference.

Templating

Adds a clipart centered in the template and with proper scaling (half the template size)

doc.addClipart$(clipart) .then(...

Replaces a clipart taking care of keeping the user transform, placing and size.

doc.replaceClipart$(old, clipart) .then(...

Figures

There are five figure types: Cx.Text, Cx.Shape, Cx.Raster, Cx.Group and Cx.ProxyGroup. They all share the same base: Cx.Figure. These are the common properties and functions.

Properties

Properties Type Def Definition
name String null Figure name
xType String 'CadX.Figure' Figure type
pen Pen empty Figure outline
brush Brush empty Figure fill
alpha 0…1 1 Global alpha
locked Boolean false If true, the figure can not be selected
hidden Boolean false If true, the figure is not visible
visible Boolean true Opposite of hidden
anchor Anchor Relative (0.5,0.5) Figure anchor generator
preMatrix22 Matrix22 Identity Transformation applied before processes
processes Process Array [] Figure effects
matrix22 Matrix22 Identity Figure transformation
pin Point (0, 0) Figure position
powerClipContents PowerClipContent Array [] Power clipped figures
metadata Metadata empty App metadata

Read only properties

Properties Type Def Definition
localId Integer unique Client based unique id, not serialized
canBeSelected Boolean true Is visible and is not locked
outputFigure Figure null Processed figure output
grouped Boolean false The figure is inside a group
isEmpty Boolean false Text with no lines, shape with not polyregions, group with no figures
layer Layer - Layer the figure is part of
topFigure Figure - Top input figure, i.e. main group

Functions

f.clone();
f.toXml();
f.toXmlString();
f.lock();
f.commit$();
f.revert$();
f.update$();
f.invalidate();
f.invalidated();
f.processed();
f.currentlyProcessing();

Transformations

f.translate(offset);
f.translate(dx,dy);
f.scaleFrom(point,sx,sy,deg);
f.rotateAround(point,angle);
f.shearAround(point,sx,sy,deg);
f.transform(matrix);
f.angle();
f.angle(angle,pivot);
f.rotate$(angle);
f.scale$(sx,sy);
f.mirrorX$();
f.mirrorY$();
f.align$(alignment,to,margin);
f.fit$(bounds,config);
f.setSize$({ width, height, locked, pin });
// Options 'nothing', 'bounds', 'center', 'width', 'frame', 'frameCenter', 'frameWidth':
f.modifyKeeping$(what,func);

Color interface

f.setTaggedColors(tag,color);
f.setLabeledColors(label,color);
f.getColors();
f.replaceColor(oldColor,newColor,config);
f.uniqueColorZones(options);
f.changeColorZone(zone,value);
f.getBrushes();
f.replaceBrush(oldBrush,newBrush);
f.uniqueBrushZones(options,zones);
f.changeBrushZone(zone,value);

Power Clip

f.extractPowerClipContents();

Xml CDL

Cx.Figure.fromXml(node);
Cx.Figure.fromXmlString(string);

Conditions

Cx.Figure.condition(name,function(figure){ return boolean });
Cx.Figure.arrayCondition(name,function(figures){ return boolean });
Cx.Figure.saveConditions();
Cx.Figure.restoreConditions();

Text

var text = Cx.Text({
text: 'Hello world!',
fontSize: 50
});
Properties Type Def Definition
text String 'Text' Figure text as a single string (with \n for multiline)
lines String Array ['Text'] Figure text lines
font Font Arial Font
fontSize Number 54.2 Font size
fontAutoSize Boolean true Size is adjusted using the M height
lineDirection Angle 0 Direction of the text line
charDirection Angle 90 Direction of each chararacters
verticalStyle *String’ SingleLine Options: SingleLine, MultiLine
extraCharSpacing Magnitude '0' Extra space between characters
extraWordSpacing Magnitude '0' Extra space between words
extraLineSpacing Magnitude '0' Extra space between lines
columnSpacing Magnitude '0' Column spacing
columnCount Integer 1 Number of columns
linesPerColumn Integer 0 Lines per column, 0 is disabled
linesAlign String 'Center' Options: 'Left', 'Center', 'Right'
fillMode String Winding Options: CadX, Winding, Alternate
powerClipId String null Power clip content link
textParts TextPart Array [] Text Part definition if mutli part

Member Functions

text.isEmpty(); // return text == ''
text.isMultiPart(); // return textParts.length > 0
text.breakIntoParts(); // return Text Array equivalent to the text parts
text.addPowerClipContents(figures); // Add figures to the powerclip content

Multi Part Text

var range = Cx.TextRange({ start: 1, end: 0 });
range.slice(text); // return text range
range.clone();
range.isEqual(other);
var part = Cx.TextPart({ textRange: range, ... text properties ... });
part.clone();
Cx.Figure.autoCreateMultiPartText$(figures) .then(function(multiPartText) { ...

Shape

var shape = Cx.Shape({
polyregions: [...],
brush:{ color:{ rgb: '000000' }}
});

Properties

Properties Type Def Definition
polyregions Polyregion Array [] Shape polyregions
shape.addPowerClipContents(figures);

Shape constructors

var rectangle = Cx.Shape.Rectangle({
bounds: Cx.Bounds(lx,ly,hx,hy)
, brush: brush, pen: pen
};
var rectangle = Cx.Shape.Rectangle({
center: Px(0,0), width: 200, height: 100
, brush: brush, pen: pen
};
var polyline = Cx.Shape.Polyline({
points: [...]
, brush: brush, pen: pen
});

Polyregion

Region

Curve

Polyline

Raster

var raster = Cx.Raster({ imageLink: '30305c17-ef10-4183-9c94-008f9a7c2d6b' });

Properties

Properties Type Def Definition
imageLink Id nul Image Id
width Magnitude null Linked image width
height Magnitude null Linked image height
dpi Number 25.4 Initial DPI
monochromeColors [Color,Color] [] For monochorme bitmaps, color for 0 and 1
raster.isMonochrome();
raster.globalDpi$();
raster.globalDpi_();

Group

var group = Cx.Group.create(figures);

Properties

Properties Type Def Definition
figures Figure Array [] Grouped figures

Member Functions

group.ungroupedFigures(); // return equivalent ungrouped figures
group.regroup(figures); // Add equivalent figures to the group
group.ungroup(figures); // return and remove figures ungrouping them
group.ungroup(); // return and remove all figures, leave the group empty

Helpers

Cx.Group.create(figures); // Create group with equivalent figures

ProxyGroup

var proxy = Cx.ProxyGroup({ link: '...' });

Properties

Properties Type Def Definition
link String null Linked Document Id
width Magnitude null Initial Width
height Magnitude null Initial Height
colors Color Array [] Colors of the linked figures

Canvas Actions

Canvas Actions

Canvas offers manipulation functions to modify the document and ask the user to use .commit$() when the transaction is finished:

var doc = canvas.document();
var parts = canvas.selectedFigures();
var group = Cx.Group.create(parts);
doc.replace(parts,group);
doc.commit$();

Canvas will automatically lock itself, give feedback to the user and render the results after the transaction is committed.

To further help in the building process of new clients, Canvas API provides cooked in actions that covers the most common needs of a template application.

Instead of the code above, you can just wrote canvas.action('group') to get a function that will group the selected figures when called. You could use it like:

$('#groupButton').click( canvas.action('group') );

Actions are function objects and expose useful query functions:

var action = canvas.action('group');
var id = action.id(); // => 'group'
var info = action.info(); // later...
var tooltip = action.tooltip(); // later...
if( ! action.enabled() ) {
// Disable your UI button
}

When you create an action, you can modify its target. Instead of applying the action to the selected figures, you can choose to target the context figures (defined as the selected figures when the selection in not empty and all the figures in the layer otherwise).

var action = canvas.action('group', { target: 'contextFigures' } );

You can also give a callback that will be issue once the action is complete and everything has been rendered in the Canvas:

var action = canvas.action('group', {
callback: function(group) {
// Do something specific to your client with
// the newly created group...
},
scope: this
});

The current list of build in actions is (these are the functions exposed in the selection toolbar in the HUD, the list will quickly grow to cover transforms and other usual actions):

  • createMultiPartText
  • breakMultiPartText
  • group
  • ungroup
  • editEnvelope
  • editPowerClip
  • zoomIn
  • duplicate
  • remove

Promises

Working with async operations is more complex than normal sync code mainly because:

  • You need to be able to wait for the results to be generated, for example if you need to communicate with a distant server in order to produce them.
  • You also need to be able to react to failure conditions that are outside of your control, the server may be down or unable to process your request.

The usual way to tackle this is using callbacks. You pass a function that will be called with the generated result when the work is done. If anything went wrong, you need to inform the error through a flag in the callback. An usual async function will look like:

var async1 = function(pa,pb,callback) {
setTimeout(function(){
// do async work with pa and pb, generate result and then...
if( error && callback ) {
callback( null, 'something went wrong' );
return;
}
// return result
if( callback ) callback( result );
},1000);
};
var async2 = function(callback) { ... };

And you will use it as:

async1(pa,pb,function(result,error){
if( result ) {
// Do something after we are done
}
else {
// Handle error condition
}
});
async2(function(result,error){ ... });

This is fine and it works. But when you try to compose code using this pattern things gets out of hand quickly. The simplest way to see the hidden complexity is try to wrap one of this functions to be able to process the result before it gets to the hands of the caller:

var asyncWrapper = function(pa,pb,callback) {
async1(pa,pb,function(result,error){
if( callback ){
if( result ){
// process and generate new result
callback( myResult );
}
else {
callback( null, error );
}
}
});
};

We need to forward the error condition from the wrapped async function so the caller of asyncWrapper can also react.

The promises pattern

The promises pattern is a formalization of async operations handling that allows the creation of composable code, simplifying these kind of scenarios. The idea is simple, instead of passing a callback in the parameters you return a Promise object from your async operation that represents the future value to be generated.

var promisedValue = asyncOperation$(pa,pb);

The promise will be resolved later on time when the value is ready, or it will be rejected if something went wrong at some point. To be able to get the value you attach a callback using the done function that will be issued when the operation is done:

promisedValue.done( function(value){
// the operation is done, value is ready to be used
});

There is no need to ask if there is actually a value inside the done callback because it will only be called if the operation is successful. To handle error conditions you add a fail callback like:

promisedValue.fail( function(error){
// handle failure condition
});

We do not need to create a named variable for the promised value, and both done and fail function will return back the promise so you can chain the calls directly like:

asyncOperation$(pa,pb).done( function(value){
// the operation is done, value is ready to be used
}).fail( function(error){
// handle failure condition
});

Til here, we didn’t gain much, the code looks similar to the one using callback in the parameters. An initial small win is that now callback are not longer mixed with other function parameters.
But the formalization of the promise object is the key to attach extra functionality that will allow us to easily compose operations that uses this pattern.

Chaining (then)

The first important extension is the then function. When we created the asyncWrapper, we had the need to be able to process the generated value in an async operation before the user can use it. In the promises world, you can do it by attaching a filter that will be executed when the value is ready, let you create something new using it and pass that value along the chain of async operations. The function we coded before can be rewritten as:

var asyncWrapper = function(pa,pb) {
return asyncOperation$(pa,pb).then( function(value) {
// process and generate new result
return myValue;
});
};

The then function takes a doneFilter, and wrap the promised value returning a new promise where you can later attach new callbacks. If we have used named variables for each promise, it would look like:

var asyncWrapper = function(pa,pb) {
var promise1 = asyncOperation$(pa,pb) ;
var promise2 = promise1.then( function(value) {
// process and generate new result
return myValue;
});
return promise2;
};

You can also return a promise yourself inside the filter allowing the piping or chaining of async operations. The newly created promise will also be rejected automatically if something went wrong in any of the operations before. We do not need to place any special error treatment in the asyncWrapper, the caller will be able to attach a fail callback without problems. If you ever need to filter the error condition to attach extra information, the then filter.

To showcase what then buys us, lets analyze an algorithm where we need to execute one async operation chained after the other and give the user the chance to know when both operations are done. Using the callback parameter we will do it like:

var chainedAsync = function(callback) {
async1(pa,pb,function(result1,error1){
if( result1 ) {
// Do something after async1 and then call
async2(function(result2,error2){
if( result2 ) {
// do something when async2 is done
if( callback )
callback(myResult);
}
else if( callback )
callback(null,error2);
});
} else if( callback )
callback(null,error1);
});
}

The same operation, using promises and then looks like:

var chainedAsync$ = function() {
return async1$(pa,pb).then( function(result1){
// Do something after async1 and then call
return async2$().then( function(result2){
// do something when async2 is done
return myResult;
});
});
}

You can see how much clean the code looks now that we do not need to deal with error propagation because it is being done for us. We can also just use result1 and result2 because we know that the doneFilter will only be issue when things when right. And because chainedAsync$ is returning a promise, the user can continue to chain whatever needs to be done after the second async operation is done.

Understanding done and then

It is important to understand the difference between done and then. The done function adds a callback that will be issue when the value is ready to be consumed and returns the same promise so you can continue to work with it.

operation1$()
.done( function(value1){ // (a)
// Do something when operation1$ is done
})
.done( function(value1){ // (b)
// This will also be called when operation1$ is resolved.
});
operation1$() => p1
|__ done -> (a)
|__ done -> (b)

On the other hand, then introduces a filter that will let you process the generated value and return a new promise:

operation1$()
.then( function(value1){ // (a)
return operation2$(value1);
})
.done( function(value2){ // (b)
// This will be called when operation2$ is done.
});
operation1$ => p1
|__ then -> (a) operation2$ => p2
|__ done -> (b)

Concurrent operations (when)

Another usual task when working with async operations is to issue several concurrent operations and do something when all of them are done. You can be loading different resources to generate your result for example. To be able to wait for jobs concurrently, we needed to do something along the lines of:

var concurrentAsync = function(callback) {
// We are going to wait for async1 and async2
var count = 2;
var results = [];
var gatherResults = function(i,result,error) {
results[i] = result;
errors [i] = error;
if( count-- == 0 ){
// Both are done...
// do something here and return
callback( myResult, myError );
}
};
async1(pa,pb,function(result1,error1){
gatherResults(0,result1,error1);
});
async2(function(result2,error2){
gatherResults(1,result2,error2);
});
}

We have an abstraction in jQuery that simplifies these kind of tasks: the $.when function takes an arbitrary number of promises and produces a new promise that will be resolved when all operations are done. If any of the concurrent promises gets rejected, the created promise will be rejected and your fail callback will be issued.

var myAsync$ = function() {
return Cx.when( async1$(pa,pb), async2$() ).then(function( result1, result2 ) {
// do something here and return
return myResult;
});
}

One of the strength of promises is that once you start using promises, making composed async operations is a lot easier (once you understand how this works, obviously).

Creating promises (Deferred)

Til here, we have always used operations that were already returning a promise. To create a promise object jQuery exposes the Deferred object. You do it like:

var operation$ = function(pa,pb) {
var dp = Cx.Defer();
// perform async work and then...
setTimeout(function(){
dp.resolve( value );
// if anything went wrong
dp.reject( error );
},1000);
return dp.promise();
}

You first create a deferred object using the Cx.Defer() constructor. When the operation is done and you have generated the value to be returned you call the resolve function. If something went wrong you trigger the fail using the reject function.

The last line uses the promise function to return a stripped down version of the Deferred object that do not expose the mutator functions resolve and reject.

Abstracting in the async world

We want to create a LightBox (a showcase box for a figure) using something along the lines of:

// Add Light Box
figure.bounds$() .then(function(bounds){
var lbb = Cx.Shape.CreateRectangle({
bounds:bounds.inflate(50,50), brush:...});
var lightBox = Cx.Group.create([lbb,figure]);
doc.add( lightBox );
canvas.zoomToFit$() .then(function(){
doc.commit$();
});
});

A first step to make this code more readable and, more importantly, to allow reuse of code is to modularize parts of the work in the same way you will do it in a sync world. Imagine we could avoid the need for
async operations, the code above will look like:

var bounds = figure.bounds();
var lbb = Cx.Shape.CreateRectangle({
bounds:bounds.inflate(50,50), brush:...});
var lightBox = Cx.Group.create([lbb,figure]);
doc.add( lightBox );
canvas.zoomToFit();
doc.commit();

In this case, there is an abstraction screaming to be made:`

App.createLightBox = function(figure,margin) {
var bounds = figure.bounds();
var lbb = Cx.Shape.CreateRectangle({
bounds:bounds.inflate(margin,margin), brush:...});
return Cx.Group.create([lbb,figure]);
};

Now we can just code the above functionality like:

var lightBox = App.createLightBox(figure,50);
doc.add( lightBox );
canvas.zoomToFit();
doc.commit();

The same thing can be done in the async world to keep the complexity as low as possible and to let others use the functionality we are creating. Promises are a great tool to allow you to easily refactor out reusable parts of your functions. We can create an async operation to create the light box like:

App.createLightBox$ = function(figure,margin) {
return figure.bounds$() .then( function(bounds){
var lbb = Cx.Shape.CreateRectangle({
bounds:bounds.inflate(margin,margin), brush:...});
return Cx.Group.create([lbb,figure]);
});
}

And you can now rewrite the original as:

App.createLightBox$(figure,50).done( function(lightBox){
doc.add( lightBox );
canvas.zoomToFit$().done( function(){
doc.commit$();
});});

That using this indentation looks exactly the same as the sync version, even with the two async operations being performed:

var lightBox = App.createLightBox(figure,50);
doc.add( lightBox );
canvas.zoomToFit();
doc.commit();

We are using the done function because we are not interested in chaining or creating a callable function. This may be the operation executed when you press a button so nobody will be waiting for the result. You could have used then without problems, and normally you will want to use then for each middleware function you are creating so your users can compose your functions with all other promise based operations.

Owned Objects

Cx objects are generally owned by other objects in the document model. For example, the figures in a layer are owned by it and the brush in a contour belongs to that process. These objects implements a owned object protocol to avoid aliasing problems while modifying the document.

When a new object is created, it doesn’t belongs to any other object. So the following snippet will create a free brush:

var brush = Cx.Brush( { rgb: 'FF0000', alpha: 0.5 } );
Cx.assert( brush.owner() == null );

When a free object is used to set another object property, the free object is adopted by the parent object without making any copy:

contour.brush( brush );
// brush is no longer free, contour owns it
Cx.assert( brush.owner() == contour );

If an owned object is used to set a property, the object will be cloned to avoid duplicated references:

contour.brush( figure.brush() );
// contour's brush and figure's brush are different objects, owned by different parents.
Cx.assert( contour.brush() != figure.brush() );

The protocol was selected for being a good compromise between simplicity and efficiency (a possible option could be to use copy-on-write for efficiency or copy-on-set for simplicity). Free or owned objects shouldn’t be something you are thinking on while using the API. It just works, and you can relax about when to clone and object… you never need to do it, the model knows better than us how to avoid getting into troubles. The only caveat is that the following snippet could be confusing:

var brush = figure.brush();
// ...
contour.brush( brush );
brush.color( color ); // Wrong!
// contour's brush and brush are different objects, owned by different parents.
Cx.assert( contour.brush() != brush );

The way to avoid these patterns is:

contour.brush( figure.brush() );
var brush = contour.brush();
brush.color( color ); // Good...

But if you can not use the above pattern, you can add the clone explicitly:

var brush = figure.brush().clone();
Cx.assert( brush.owner() == null );
// ...
contour.brush( brush );
brush.color( color ); // Good...

Transaction Model

Canvas is a scripting language to manipulate documents (templates). It is not only intended to use as a internal model to be able to render things in the browser; you can use it to load documents from the server, modify them and send them back with out showing in the browser nothing more than a progress bar. You can do batch updating of things in the DB like this without the need of asking the server Core team to do it for you. All the power of the engine is there for you to use it in your local machine.

Things can fail

Because of the nature of the interaction between CxEngine in the server and Canvas in the client, some operations are async and there are more places where failures can appear: the internet may be down, the server may be overloaded, etc. Even when you do simple scripting it is useful to be able to work using transactions. Being able to mark valid points in your work allows for a more fine grained treatment of failures. You do not need to redo everything, it is enough to go back til the last valid committed state and do the last part that has failed. In this context, transactions are an important piece of CIL design and are not related to the need of a History feature for the user. You can use CIL without doing any transaction, nobody will force you to do it, transactions are a natural part of the design. A good analogy is to think that you are working with something similar to a revision control system (an old one where you need to explicitly lock resources. So, if you need to modify a figure you will do it like:

figure.lock();
... load resources, modify the figure ...
figure.commit$();

The first lock let others know that this figure is being working on by throwing a document event. Our system allows you to work with figures in parallel, but you can not modify the same figure in parallel.
If you modify the whole document, you will write:

doc.lock();
... modify the doc ...
doc.commit$();

Modifying CIL objects is like changing files in your local repository in the revision system. Your local files (model) are changed until you reach an atomic change set. When you reach that point, you call the .commit$() function to complete the transaction.

If something goes wrong in the middle, you can easily revert your local changes back to the last committed state like:

doc.lock();
... modify the doc ...
... something went wrong ...
doc.revert$();

An important thing to understand here is that the lock/commit$ mechanism is just a transaction scheme and does not generate by itself any history, there is no redo/undo in the picture yet. The mechanism just saves the last committed state to be able to go back to it when you do a revert, so you can program against a failure prone system (like it is the internet, server infrastructure, etc) with better tools. So til here, there is no history, no rendering, no tools disabling, no user feedback. The above description is about our template scripting language.

Canvas

Lets put the Canvas component in the table. This is the main piece used to build browser applications that needs to render templates or interact with user. Now, on top of being able to modify a document, we need to do more things to give a good user experience. The minimal set of things to take care of are:

  • (a) Render the document at moment where it makes sense (avoid showing the guts of batch operations to the user… no jumps around, no flickering because you first loaded something of a given color and later you changed it, etc).
  • (b) Disable all other tools when an operation is being performed so there are no race conditions. Again CIL only allows you to modify one figure at a time.
  • (c) Give visual feedback to show that an operation is being performed, and that the user shouldn’t keep hitting the screen to make it “do something”.
  • (d) Maintain a history of all the operations performed so the user can undo/redo.
  • (e) Select newly added figures.

A possible design is to let the developer deal with these issues. He will need to write code like:

0. canvas.disable(); // b
1. canvas.startFeedback(); // c
2. doc.lock();
3. ... modify the doc ...
4. canvas.selectedFigures(newFigures); // e
5. doc.commit$();
6. history.push(doc); // d
7. canvas.stopFeedback(); // c
8. canvas.enable(); // b
9. canvas.render$(); // a

This is unacceptable, imagine if for just moving a figure we will need almost 10 lines of code.

Canvas tackles this problem by defining that is going to show every committed state of the document. This makes sense because the state at the end of every transaction effectively represent the successful completion of an atomic change set. Canvas listens to the document ‘commit’ event and calls render after it avoiding the need of line 9.

The tools will also be disabled when an operation starts (that is, when someone locks the document to start working on a new change set). Once the change is complete after the ‘commit’, we can safely enable the tools back. Line 0 and 8 can be eliminated.

The same is done for user feedback. As soon as somebody starts an operation we show the user feedback (on ‘lock’) and when the operation is complete we just stop the work in progress feedback (on ‘commit’). Line 1 and 7 are automated.

History is defined as every committed state of the document. Because rendering happens at that point, this means that the user will get a history point for every operation he see completed on the canvas. Canvas automatically creates a history point when the ‘commit’ event is issued removing the need for line 6.

Newly added figures can also be automatically selected when the transaction is done. This creates a standard behavior for user created actions that helps making the UI more coherent. We can then remove line 4.

So, the code to make a proper action ends up being:

doc.lock(); // => Trigger 1, 2
... modify the doc ...
doc.commit$(); // => Trigger 4, 6, 7, 8, 9

The user can just code its action in the same way that he will do to create batch scripts. There is nothing new to learn. Canvas will automatically work.

It makes even more sense when possible failures are analyzed. Every time you call revert$(), Canvas will also be re-enabled and the work in progress feedback will be stopped. No rendering and history needs to be generated here.

An important implication is that clients have a clear pattern to follow to create pre or post-operation events. The client may have other tools to disable/enable that are not part of Canvas. They can listen to the ‘lock’ and ‘commit’ events in the same way that Canvas tools do. Other kind of feedback may be wanted (a progress bar, a loading symbol, a timer). Also, property watchers like a display for the document bounds, or a label showing the current price for the design can no be easily automated to be updated after each commit.

Further details on the lock/commit$ pattern

Conceptually .lock() and .commit() are not really a pair, although they will usually use together. Locking has to do with a limitation of our system, we can no modify the same figure at the same time by two different actions. When you start an action, you have to lock your target (doc or figures). But if your action starts with a modification, you can omit the .lock() call because each modifier will auto-lock like:

figure.pin( ... ); // => auto-lock
figure.commit$(); // => auto-unlock

If you need to load resources before modifying the document in your action you need to be explicit and lock the document:

doc.lock();
... load resources, modify doc ...
doc.commit$();

Our system works like Git in the sense that a commit only modifies the local copy and not the saved document in the server. If you think about the pattern of loading a document, performing some modification and the save it back, then our system will map to Git as follow:

Cx.Document.load$(id) // Git 'checkout'
.then(function(doc){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
doc.save(); // Git 'push'
}); }); }); });

Invalidation

Figure measurements and the processing needed to obtain its output representation are asynchronous and possibly expensive operations. When you change a property of the figure, for performance reasons, Canvas will try hard to avoid performing the whole computation again by updating its internal measurements and output representation locally whenever possible. Expensive updating operations will not be triggered every time the model changes. The figure remembers its invalidation state waiting for the next update$. Model changes work in batch:

figure.pin( canvas.center() );
figure.matrix22( Mx.scale(2,2) );
figure.brush( Cx.Brush({ rgb: 'FF0000' }) );
// Now the figure is in an invalid state, measurements and the output representation
// that is rendered in Canvas are outdated
figure.update$() .then(function(){
// Now the figure measurements and its output representation are up to date,
// but the Canvas has not been touched
});

The model will automatically invalidate itself when using the public API so all these optimizations will work behind the scene and apps shouldn’t need to worry about them. But direct manipulation of primitive types needs a manual call to invalidate:

figure.pin().x = 20;
figure.matrix22().s00 = 2.0;
// figure is in an undefined invalidation state now
figure.invalidate();
// good, everything will work now

Avoid at all cost this pattern, apps should not need any .invalidate call at all if they treat primitive types as whole values always. You have to think that a point is actually equivalent to a number or a string in JavaScript. We can not expect that the model will work if you change a character in an array like this:

figure.name()[3] = 'a'; // this should strike you as the same as figure.pin().x = 20

So every time you change a point, a matrix or other primitives types in the model, just use them as immutable objects passing around:

var center = canvas.center();
figure.pin( center );

The list of primitives types in Canvas API is: number, integer, angle, magnitude, string, point, matrix, frame and bounds. They are easy to recognize because their members are plain JavaScript variables (.x and .y, .p0, .s00, etc).

Owned mechanics

Cx objects are generally owned by other objects in the document model. For example, the figures in a layer are owned by it and the brush in a contour belongs to that process. These objects implements a owned object protocol to avoid aliasing problems while modifying the document.

When a new object is created, it doesn’t belongs to any other object. So the following snippet will create a free brush:

var brush = Cx.Brush( { rgb: 'FF0000', alpha: 0.5 } );
Cx.assert( brush.owner() == null );

When a free object is used to set another object property, the free object is adopted by the parent object without making any copy:

contour.brush( brush );
// brush is no longer free, contour owns it
Cx.assert( brush.owner() == contour );

If an owned object is used to set a property, the object will be cloned to avoid duplicated references:

contour.brush( figure.brush() );
// contour's brush and figure's brush are different objects, owned by different parents.
Cx.assert( contour.brush() != figure.brush() );

The protocol was selected for being a good compromise between simplicity and efficiency (a possible option could be to use copy-on-write for efficiency or copy-on-set for simplicity). Free or owned objects shouldn’t be something you are thinking on while using the API. It just works, and you can relax about when to clone and object… you never need to do it, the model knows better than us how to avoid getting into troubles. The only caveat is that the following snippet could be confusing:

var brush = figure.brush();
// ...
contour.brush( brush );
brush.color( color ); // Wrong!
// contour's brush and brush are different objects, owned by different parents.
Cx.assert( contour.brush() != brush );

The way to avoid these patterns is:

contour.brush( figure.brush() );
var brush = contour.brush();
brush.color( color ); // Good...

But if you can not use the above pattern, you can add the clone explicitly:

var brush = figure.brush().clone();
Cx.assert( brush.owner() == null );
// ...
contour.brush( brush );
brush.color( color ); // Good...

Positioning

In CDL documents figures are placed by centering the Anchor reference point to the defined Pin position.

Figure Pin

Whenever a figure is added to a document, its Pin is defined as the (0,0) coordinate. You can also set it at the figure definition:

var figure = Cx.Text({ line: "Phrase"
, ...
, pin: Px(100,50) });

Or change it later using the pin property as usual:

figure.pin( Px(100,20) ); // Or directly figure.pin(100,20);

Also we may change the position of a figure with respect to its current position:

figure.translate( offset ); // Or figure.translate( x,y );

This would change the figure’s position in x horizontally and y vertically.

Other operations can also modify the figure’s position, for example rotation around a given pivot point:

figure.rotateAround(pivot,degrees);

Figure Anchor

To be able to place a given figure in the Pin position we need to define a reference point relative to the figure curves. This point is called Anchor in our system and it is an important piece in building well behaved templates that the user can modify.

The following diagram illustrates the relationship between the Anchor and the Pin. We start with a given shape that is defined by a single Region with a closed boundary. The original points in this curve are the ones in the upper-right corner of the document. The green point is the Anchor of the Shape, in this particular case is an Absolute Anchor. This is the point that will be used to center the figure in the Pin position, sketched in the diagram with a red dot.

Basic Anchor

shape.anchor( Cx.AbsoluteAnchor(pos) );

We use a Relative Anchor to define a good reference point for Texts for example. The reference point will be obtained relative to the bounds of the processed figure (before the final transformation is applied).

text.anchor( Cx.RelativeAnchor(rx,ry) );

The following diagram showcased what happens when the user modify the line of a given Text. The Anchor is defined as Cx.RelativeAnchor(0.5,0.5) in this case. So the center of the bounds is placed at the pin position marked here again as a red dot. The figure accommodates nicely around the Pin.

Text changed

The Anchor of the figure is an object that defines an absolute and a pos properties.

var anchor = figure.anchor();
if( anchor.absolute() ) {
var asolutePos = anchor.pos();
// ...
}
else {
var relativePos = anchor.pos();
// ...
}

Direct positioning

Sometimes, it is useful to avoid the Anchor-Pin model and directly work with the figure bounds. We can use center or align operations to define the pin with respect to other points or objects in the document.

figure.center$(pos); // Or figure.center$(x,y);

The center$ function takes any objects that defines a center (Canvas, bounds, frame or even a list of figures). For example, we can place the figure in the middle of the view with:

figure.center$(canvas); // Center the figure in the middle of Canvas

Read the figures transformations reference to learn about more available alignment operations.

Metrics

Modifying the model is always a sync operation. Normally there is no need to care about the async nature of the interaction between Canvas and the server. For example:

figure.translate(10,0);
figure.rotateAround(figure.pin(),45);
figure.commit$();

On the other hand, if metrics are needed to define the values that you are going to change then we are in promises land. Getting metrics is an async operation in Canvas.
As every async operation in Canvas, metrics functions are marked with a trailing $ and will return a promise for the computed value:

figure.center$() .then(function(center){
figure.rotateAround(center,45);
figure.commit$();
});

Figure metrics

Name Type Definition
matrix$ Matrix Transformation applied to curve points
bounds$ Bounds Bounds of the rendered figure
center$ Point Center of the figure bounds
frame$ Frame Frame of the rendered figure
vectorBounds$ Bounds Curves Bounds, pens dropped
vectorFrame$ Frame Curves Frame, pens dropped

Pre metrics

The figure frame is computed transforming with the matrix the bounds of the untransformed figure. We call the metrics of the untransformed figure pre metrics. These metrics are needed for some operations like envelope equalization.

Name Type Definition
prePin$ Point Untransformed pin, figure anchor
preBounds$ Bounds Bounds of the untransformed rendered figure
preCenter$ Point Center of the figure pre bounds
preVectorBounds$ Bounds Untransformed Curves Bounds, pens dropped

Global metrics

Grouped figures are then transformed by their parents matrix. Global metrics provide an easy way to know the final bounds of a figure even if they are grouped. If the figure is not grouped, global metrics will be equal to the normal metrics. The global version of the metrics are: globalMatrix$, globalBounds$, globalCenter$, globalFrame$, globalVectorBounds$, globalVectorFrame$, globalPreBounds$ and globalPreVectorBounds$.

Examples

[TODO]

Why metrics are async operations?

Canvas JavaScript engine (Cx) is unable to reproduce every operation supported by the CadWorX CadXEngine server. Only a subset of CDL documents can be completely handled locally. For example, a Text will need the server to process the fonts, generate the vector representation of the characters and arrange them to get to its final output vector shape representation. We call updating to the act of computing the figure’s output representation (composed of only simple shapes and raster figures). Because Cx may need the help of CadXEngine server, updating a figure is an asynchronous operation. Measuring geometry figure properties like bounds and frame requires the output of the figure to be up-to-date.

When using metrics, Canvas takes care of updating the figure behind the scene. Figure updating can also be triggered manually using:

figure.update$() .then(function(figure){
// ...
});

If you already know the figure is up-to-date, you can use synchronous versions of the metrics. The set of functions that needs the figure to be updated are marked with a trailing underscore sign, underscore so developers can easily identify them: bounds, frame, center_:

figure.update$() .then(function(figure){
var b = figure.bounds_();
var f = figure.frame_();
// ...
});

These may be useful in some contexts, but always go for the async version of the metrics first. Calling any metric_ function when the figure is invalidated (its output has not been updated) will result in an exception. As reference, internally metrix$ is implemented like:

metrix$: function(){
return this.update$() .then(function(figure){
return figure.metrix_();
});
}

Transformations

Every transformation method returns the figure or figures that were transformed so you can chain them.

Basic transformation on figures, the deg parameter in scaleFrom and shearAround applies the transform in a rotated space.

figure.translate(x,y); // figure.translate(offset);
figure.scaleFrom(point,sx,sy [,deg]);
figure.rotateAround(point,angle);
figure.shearAround(point,sx,sy [,deg]);
figure.transform(matrix);
Cx.translate(figures, x,y); // Cx.translate(figures, offset);
Cx.scaleFrom(figures, point,sx,sy [,deg]);
Cx.rotateAround(figures, point,angle);
Cx.shearAround(figures, point,sx,sy [,deg]);
Cx.transform(figures, matrix);

Transformations using the center of the figures bounds as the pivot. We need to use metrics here so these operations are asynchronous.

figure.rotate$(angle);
figure.scale$(sx, sy);
figure.mirrorX$();
figure.mirrorY$();
Cx.rotate$(figures, angle);
Cx.scale$(figures, sx, sy);
Cx.mirrorX$(figures);
Cx.mirrorY$(figures);

Alignment

figure.center$( to );
Cx.center$( figures, to );

Center a figure or a figures list. to can be a point or anything that has a center (bounds, frame, other figures).

figure.align$( alignment, to [, margin] );

Align the figure against a given bounds.

  • alignment any combination of Left, Center, Right with Top, Middle, Bottom. Examples: “TopLeft”, “Center”, “Bottom”, “CenterMiddle”. It doesn’t matter the order or if you put a space or a comma between them (this is ok: “Top Right”).
  • to a bounds object of something we can get bounds from (canvas,frame,other figures).
  • margin optional margin for the alignment
Cx.align$( figures, alignment [, to, margin] );

Align a group of figures to a given bounds. If you call it without a to parameter it will align the figures against themselves.

Cx.groupedAlign$( figures, alignment, to [,margin] );

Align the figures without loosing their relative distances.

Examples:

// Center figures in Canvas
Cx.center$( figures, canvas );
// Align figures to the top of the Canvas (respecting relative distances)
Cx.groupedAlign$( figures, 'Top', canvas );
// Align figures baselines
Cx.align$( figures, 'bottom' );

Fitting

Cx.fit$(figures, to,{ locked:[bool], fit:['Max'/'Min'/'Mean'] });

Fits a group of figures to a bounds object or anything that has bounds.

Examples:

// Fit figures to cover the Canvas visible area
Cx.fit$(figures, canvas);
// Fit figures to cover all the figures in the document
Cx.fit$(figures, doc.activePageFigures());
Cx.setSize$(figures, { width:[n], height:[n], locked:[bool], fit:['Max'/'Min'/'Mean'], pin:[point] });

Examples:

// Set the width of the selected figures, preserving aspect ratio.
Cx.setSize$(canvas.selectedFigures(),{width:400})
// Set the width and height of the figures, preserving aspect ratio and area of the bounds.
Cx.setSize$(figures,{width:500,fit:'Mean'});
// Set width and height, do not preserve aspect ratio
Cx.setSize$(figures,{width:100,height:100,locked:false});

Boolean operations

Cx.reshape$(figures,action) .then(function( newFigures ) {
// ...

Performs a reshape action on a list of figures. The action can be any of Cx.E.ReshapeAction: ‘Combine’, ‘Weld’, ‘Condense’, ‘FrontMinusBack’, ‘BackMinusFront’, ‘BreakApart’, ‘BreakApartRegions’ and ‘BreakApartByColor’

Cx.combine$(figures) .then(function( newFigure ){ ...
Cx.weld$(figures) .then(function( newFigure ){ ...
Cx.condense$(figures) .then(function( newFigure ){ ...
Cx.frontMinusBack$(figures) .then(function( newFigure ){ ...
Cx.backMinusFront$(figures) .then(function( newFigure ){ ...

Direct functions for combine type operations. Get a list of figures and return one composed figure.

Working with colors

Cx.forEachColor(figures,function(color){ ... },context);

Iterate through every color of a list of figures.


var zones = Cx.uniqueColorZones(figures, { collapseTags:[bool] });

Returns a list of unique color zones composed of a solid color (both from pens and brushes) and with textures. Each zone has a value member that can be a Cx.Color or a Cx.TextureBrush.


Cx.changeColorZone(figures, zone, newValue);

Replaces a color zone with a new value.

var map = function(color) { return newColor -or- color; };
Cx.convertColors(figures, map);

Converts the colors in the figures using the function map.

Cx.colorAt$(figures, point).then( function(color) ) {
// ...

Gets the color at the given point for a list of figures.

Brushes and Pens

When reading the [[introduction]] you might have noticed among the parameters to initialize the figure that a brush is specified.

For each figure different kinds of brushes and pens can be specified and changed by changing the corresponding [[properties]].

Check the pens and brushes sample app ([[brushespens.html|https://github.com/Stahls/CadXDocumentation/blob/master/brushespens.html]] and [[brushespens.js|https://github.com/Stahls/CadXDocumentation/blob/master/brushespens.js]]) for some examples.

Brushes

figure.brush(brush);
doc.commit$();

Brush types: Brush, LinearGradientBrush, RadialGradientBrush, TextureBrush.

Brush

A regular solid color brush.

Brush Type Def Definition
color Color Black Sets the brush color
Cx.Brush({ color: { rgb: '000000' } });
<Brush><Color RGB="000000"></Color></Brush>

Linear Gradient Brush

Forms a linear gradient using the specified colors and points.

LinearGradientBrush Type Def Definition
colors Color Array [ White, Black ] Gradient Colors
positions Number Array [ 0,1 ] Range (between 0 and 1) of the figure that each color will cover
from Point (0.0,0.5) Relative point where the gradient begins
to Point (1.0,0.5) Relative point where the gradient ends
gammaCorrection Boolean true Gamma correction
Cx.LinearGradientBrush({ from: Px(0.0,0.5), to: Px(1.0,0.5)
, gammaCorrection: true
, positions: [ 0, 1 ]
, colors: [ { rgb: 'FFFFFF' }
, { rgb: '000000' } ] });
<LinearGradientBrush From="0.0 0.5" To="1.0 0.5"
GammaCorrection="True"
Positions="0 1">
<Color RGB="FFFFFF"></Color>
<Color RGB="000000"></Color>
</LinearGradientBrush>

Radial Gradient Brush

Forms a radial gradient using the specified colors and points

RadialGradientBrush Type Def Definition
centerColor Color White Center color
outerColors Color Array [Black] Outer colors
positions Number Array [0,1] Range (between 0.0 and 1.0) of the figure that each color will cover
center Point (0.5,0.5) Sets the relative point where the gradient begins
radiuses Number 1.0 Sets the relative radiuses of the outer colors
Cx.RadialGradientBrush({ centerColor: { rgb: 'FFFFFF' }
, outerColors: [ { rgb: '000000' } ]
, positions: [ 0, 1 ]
, center: 0.5,0.5, radiuses: [1] )});
<RadialGradient CenterColor="White" OuterColors="Black" Positions="0.0 1.0" Center="0.5 0.5" Radiuses="1.0"/>

Texture brush

Adds an image based texture to the figure.

TextureBrush Type Def Definition
imageLink String null Image Id on the server
textureOffset Point (0.0,0.0) Texture relative offset
targetOffset Point (0.0,0.0) Target relative offset
matrix22 Matrix Identity Transform to apply to the image
transformMode Enum Full Options: Fixed, Clamp, RotateOnly, Full, StretchX, StretchY
clippingRect Bounds (0,0,1,1) Relative bounds of the image
Cx.TextureBrush({ imageLink: '...',
, textureOffset: Px(0,0), targetOffset: Px(0,0)
, matrix22: Mx(), transformMode: `Full`
, clippingRect: Cx.Bounds(0,0,1,1) });
<TextureBrush ImageLink="..."
TextureOffset="0 0" TargetOffset="0 0"
Matrix22="1 0 0 1" TransformMode="Full"
ClippingRect="0 0 1 1"/>

Pens

Pen types: Pen, CutPen, EmptyPen, DisplayPen, MaskPen

var pen = Cx.Pen({ width: 2, color: { rgb: color } });
figure.pen(pen);
doc.commit$();

Pen

Pen Type Def Definition
color Color Transparent Pen color
width Number 5 Pen width
startCap Enum Flat Options: Flat, Square, Round, Triangle
endCap Enum Flat Options: Flat, Square, Round, Triangle
dashCap Enum Flat Options: Flat, Round, Triangle
dashStyle Enum Solid Options: Solid, Dash, Dot, DashDot, DashDotDot
lineJoin Enum Round Options: Round, Bevel, Miter, MiterClipped
miterLimit Number 10 Miter limit
behindFill Boolean true Draw the pen behind the fill
Cx.Pen({ color: { rgb: '000000' }, width: 5
, startCap: 'Flat', endCap: 'Flat'
, dashCap: 'Flat', dashStyle: 'Solid'
, lineJoin: 'Round', miterLimit: 10
, behindFill: true });
<Pen Width="5" StartCap="Flat" EndCap="Flat"
DashCap="Flat" DashStyle="Solid"
LineJoin="Round" MiterLimit="10.0"
BehindFill="True">
<Color RGB="000000"></Color>
</Pen>

Processes

Effects can be applied to a Canvas figure using the processes array property.

var processes = figure.processes();
processes.add( process );

Some processes are restricted to specific kinds of figures.

Text processes

Can be applied only to Text.

Changes the size of the first and last letter.

Cx.Capital Type Def Definition
scaleX Number 1 X axis scale to be applied to the letter
scaleY Number 1 Y axis scale to be applied to the letter
hAlignment Enum Center Options: Left, Center, Right
vAlignment Enum Baseline Options: Baseline, Top, Middle, Bottom
pattern Enum 'First Last' Options: 'First', 'Last', 'First Last'
var process = Cx.Capital({ scaleX: 1.5, scaleY: 1.5, vAlignment: 'Middle', pattern: 'First' });
<Capital ScaleX="2" ScaleY="2" HAlignment="Center" VAlignment="Baseline" Pattern="First Last"/>

Similar to Capital but offers better control of the resizing.

Cx.BookEnd Type Def Definition
headSX Number 1 X axis scale to be applied to the first letter
headSY Number 1 Y axis scale to be applied to the first letter
tailSX Number 1 X axis scale to be applied to the last letter
tailSY Number 1 Y axis scale to be applied to the last letter
hAlignment Enum Center Options: Left, Center, Right
vAlignment Enum Baseline Options: Baseline, Top, Middle, Bottom
var process = Cx.BookEnd({ headSX: 1.2, headSY: 1.5, tailSX: 1.2, tailSY: 1.5
, hAlignment: 'Center', vAlignment: 'Bottom' });
<BookEnd HeadSX="1.2" HeadSY="1.5" TailSX="1.2" TailSY="1.5"
HAlignment="Center" VAlignment="Baseline"/>

Fits the text to an arc.

Cx.ClassicArc Type Def Definition
width Number 500 Width of the arc
height Number 100 Height of the arc
from Angle 160 Angle of the arc where the text starts
to Angle 20 Angle of the arc where the text finishes
dontRotate Boolean false Whether to rotate or not the each letter
var process = Cx.ClassicArc({ width: 1000, height: 400, from: 135, to: 45, dontRotate: false });
<ClassicArc Width="1000" Height="400" From="135" To="45" DontRotate="false"/>

Fits the text to an ellipse.

Cx.FitEllipse Type Def Definition
height Number 100 Height of the ellipse
width Number 500 Widht of the ellipse
rotation Number 0 Angle to rotate the text with respect to its center after fitting
placement Number 90 Angle to place the center of the text in the ellipse
mirror Boolean False Whether to use a mirror effect on the text or not
inside Boolean True Whether to fit the text inside the ellipse or not
dontRotate Boolean False Whether to rotate or not the text
fit Enum None Options: None, Scaled, Contained, Stretched, Expanded
var process = Cx.FitEllipse({ height: 100, width: 500, rotation: 0, placement: 90
, mirror: false, inside: true, dontRotate: false, fit: 'None'});
<FitEllipse Height="100" Width="500" Rotation="0" Placement="90"
Mirror="False" Inside="True" DontRotate="False" Fit="None"/>

Fits the text in a defined path.

FitOnPath Type Def Definition
vPos Enum Over Options: Above, Over, Below
hPos Enum Fixed_CenterTop Options: Fixed_Left, Fixed_CenterTop, Fixed_CenterBottom, Fixed_Right, Stretched, Scaled, Expanded
offset Number 0 Offset of the text and the beginning of the path
distanceFromPath Number 0 Distance between the path and the text
placeOnOtherSide Boolean False Whether to place the text on the other side of the path or not
fwd Boolean True Whether place the text forwards or backwards in the path
dontRotate Boolean False Whether to rotate or not the text
overhang Boolean False Whether to use overhang or not
//path Path - The path the text will be fitting to
var process = Cx.FitOnPath({ path: Cx.Curve(points,false)
, vPos: 'Over', hPos: 'Fixed_CenterTop', offset: 0
, distanceFromPath: 0, placeOnOtherSide: false
, fwd: true, dontRotate: false, overhang: false });
<FitOnPath VPos="Over" HPos="Fixed_CenterTop" Offset="0" DistanceFromPath="0"
PlaceOnOtherSide="False" Fwd="True" DontRotate="False" Overhang="False">
<Path Type="Bezier3" IsClosed="False" Points="..."></Path>
</FitOnPath>

Deform processes

This processes deform the figure they’re applied to.

Fits the figure to an arch.

VerticalArch Type Def Definition
height Number 100 Height of the arch
width Number 500 Width of the arc
from Number 160 Angle of the arc where the figure starts
to Number 20 Angle of the arc where the figure starts
var process = Cx.VerticalArch({ height: 100, width: 500, from: 160, to: 20 });
<VerticalArch Height="100" Width="500" From="160" To="20"/>

Makes the figure to be shown as if it was beeing looked from a certain perspective.

Cx.Perspective Type Def Definition
hPoint Point (1000, 0) Horizontal vanishing point
vPoint Point (0, 300) Vertical vanishing point
var process = Cx.Perspective({ hPoint: Px(1000, 0), vPoint: Px(0, 300) });
<Perspective HPoint="1000, 0" VPoint="0, 300"/>

Fits the figure to the form of an envelope

FitEnvelope Type Def Definition
envelope Cx.Envelope Envelope to fit the figure
mode Enum Mixed Options: Mixed, Freeform, Uniform, Boxed
vPos Enum Stretched Options: Stretched, Top, Center, Bottom, Expanded
hPos Enum Stretched Options: Stretched, Left, Center, Right, Expanded
highResolution Boolean False Whether to use or not high resolution
keepLines Boolean False Whether to keep the lines or not
addBearingX Boolean False Whether to add or not bearing x
var process = Cx.FitEnvelope({ envelope: Cx.Envelope(curves)
, mode: 'Mixed'
, vPos: 'Stretched', hPos: 'Stretched'
, highResolution: false, keepLines: false
, addBearingX: false });
<FitEnvelope Mode="Mixed" VPos="Stretched",
HPos="Stretched" HighResolution="False" KeepLines="False"
AddBearingX="False" VPoint="0 300">
<Envelope>
<Curve Type="Bezier3" IsClosed="False" Points="..."></Curve>
<Curve Type="Bezier3" IsClosed="False" Points="..."></Curve>
<Curve Type="Bezier3" IsClosed="False" Points="..."></Curve>
<Curve Type="Bezier3" IsClosed="False" Points="..."></Curve>
</Envelope>
</FitEnvelope>

Decorate processes

This processes decorate the figure (i.e. add elements like shadows or contours).

Adds a border to the figure.

Cx.Border Type Def Definition
dX Number 20 The size of the border for the x axis
dY Number 30 The size of the border for the y axis
gap Number 10 The gap between the parts of the figure if separate
separate Boolean False Whether to separate the figure or not
var process = Cx.Border({ dX: 20, dY: 30, gap: 10, separate: false });
<Border DX="20" DY="30" Gap="10" Separate="False"/>

Add a contour to the figure.

Cx.Contour Type Def Definition
pen Cx.Pen - The pen to use
brush Cx.Brush - The brush to use
offset Number 5 The offset of each contour step
steps Number 1 Number of steps of the contour
inside Boolean false Contour towards the inside
outside Boolean true Contour towards the outside
includeHoles Boolean false Include holes of the input figure
combine Boolean false Combine all parts of the contour in a single shape
chamfer_D Number 0.1 Soften the edges of the contour
chamfer_L Number 0.1 Soften the edges of the contour
var process = Cx.Contour({ pen: Cx.Pen({ width: 2, color:{ rgb: '0000FF' } })
, brush: Cx.Brush({ color:{ rgb: 'FF0000' } })
, offset: 5, steps: 1, inside: false, outside: true
, includeHoles: false, combine: false
, chamfer_D: 0.1, chamfer_L: 0.1 });
<Contour Offset="5" Steps="1" Inside="False" Outside="True"
IncludeHoles="False" Combine="False"
Chamfer_D="0.1" Chamfer_L="0.1">
<Pen Width="2"><Color RGB="0000FF"></Pen>
<Brush><Color RGB="FF0000"></Color></Brush>
</Contour>

Add a contour to the figure made with a list of pens.

Cx.PenContour Type Definition
pens Pens Array Each pen defines a step with the given width
var process = Cx.PenContour({ pens: [
Cx.Pen({ width: 5, color:{ rgb: 'FF0000' } })
, Cx.Pen({ width: 3, color:{ rgb: '0000FF' } })
]});
<PenContour>
<Pens>
<Pen Width="5"><Color RGB="FF0000"></Color></Pen>
<Pen Width="3"><Color RGB="0000FF"></Color></Pen>
</Pens>
</PenContour>

Add a shadow to the figure.

Cx.Shadow Type Def Definition
pen Pen - The pen to use
brush Brush - The brush to use
offsetX Number 5 The X offset of the shadow
offsetY Number 5 The Y offset of the shadow
gap Number 10 The gap between the figure and the shadow
simplify Boolean false Simplified output
var process = Cx.Shadow({ pen: Cx.Pen({ width: 2, color:{ rgb: '0000FF' } })
, brush: Cx.Brush({ color:{ rgb: 'FF0000' } })
, offsetX: 5, offsetY: 5, gap: 10, simplify: false });
<Shadow OffsetX="5" OffsetY="5" Gap="5" Simplify="False">
<Pen Width="2"><Color RGB="0000FF"></Pen>
<Brush><Color RGB="FF0000"></Color></Brush>
</Shadow>

Same as shadow except that this process only uses raster pens.

Cx.RasterShadow Type Def Definition
pen Pen - The pen to use
brush Brush - The brush to use
offsetX Number 5 The X offset of the shadow
offsetY Number 5 The Y offset of the shadow
gap Gap - The gap between the figure and the shadow
var gap = Cx.RasterShadowGap({ pen: Cx.Pen({ width: 1, color:{ rgb: 'FFFFFF' } })} });
var process = Cx.RasterShadow({ pen: Cx.Pen({ width: 2, color:{ rgb: '0000FF' } })
, brush: Cx.Brush({ color:{ rgb: 'FF0000' } })
, offsetX: 5, offsetY: 5
, gap: gap };
<RasterShadow OffsetX="5" OffsetY="5">
<Pen Width="2"><Color RGB="0000FF"></Pen>
<Brush><Color RGB="FF0000"></Color></Brush>
<Gap>
<Pen Width="1"><Color RGB="FFFFFF"></Pen>
</Gap>
</RasterShadow>

Gives the figure a 3d effect.

Cx.Effect3d Type Def Definition
borderIntensity Number 0.7 The border intensity
borderSize Number 1.0 The border size
contourOffset Number 1.0 The contour offset
lightGap Number 2.0 The light gap
lightIntensity Number 0.7 The light intensity
lightSize Number 3.0 The light size
lightSource Enum Up Options: Up, Right, Down, Left
shadowIntensity Number 0.35 The shadow intensity
shadowSizePercentage Number 0.95 The percentage of the shadow size
slopeIntensity Number 0.3 The slope intensity
slopeSize Number 1.5 The slope size
var process = Cx.Effect3d({ borderIntensity: 0.7, borderSize: 1.0, contourOffset: 1.0
, lightGap: 2.0, lightIntensity: 0.7, lightSize: 3.0, lightSource: 'Up'
, shadowIntensity: 0.35, shadowSizePercentage: 0.95
, slopeIntensity: 0.3, slopeSize: 1.5});
<Effect3d BorderIntensity:"0.7" BorderSize="1.0" ContourOffset="1.0"
LightGap="2.0" LightIntensity="0.7" LightSize:"3.0" LightSource="Up"
ShadowIntensity="0.35" ShadowSizePercentage="0.95"
SlopeIntensity="0.3" SlopeSize="1.5"/>

Extrude3D

Gives the figure a 3d extrude effect.

Cx.Extrude3D Type Def Definition
pen Pen - The pen to use
brush Brush - The brush to use
depth Number 10 Depth of the extruding shape
vanishingPoint Point (0,0) Vanishing point
rotationX Number 0 Rotation with respect to x axis
rotationY Number 0 Rotation with respect to y axis
rotationZ Number 0 Rotation with respect to z axis
var process = Cx.Extrude3D({ pen: Cx.Pen({ width: 2, color:{ rgb: '0000FF' } })
, brush: Cx.Brush({ color:{ rgb: 'FF0000' } })
, depth: 10, vanishingPoint: Px(0,0)
, rotationX: 0, rotationY: 0, rotationZ: 0 });
<Extrude3D Depth="10" VanishingPoint="0 0" RotationX="0" RotationY:"0" RotationZ="0">
<Pen Width="2"><Color RGB="0000FF"/></Pen>
<Brush><Color RGB="FF0000"></Color></Brush>
</Extrude3D>

Split

Splits the figure.

Cx.Split Type Def Definition
overlap Number 0 The amount of the figure to overlap when splitting
pos Number 0.5 Position where the gap will be, 0.5 is the middle of the figure
turn Number 0 Angle to rotate the gap respect to the y axis
gap Number 10 The size of the gap
var process = Cx.Split({ overlap: 0, pos: 0.5, turn: 0, gap: 10 });
<Split Overlap:"0" Pos="0.5" Turn="0" Gap="10"/>

Utilities

Raster operations

Cx.rasterize$( figures, { dpi: 25.4 } )
.then( function( rasters ){
// ...
});

Converts figures to a raster representation. By default the dpi is 25.4 * 3, allowing for some looseless up scaling afterwards.

Cx.vectorize$( rasters, {
backColors : [ 'FFFFFF' ]
, foreColors : [ '000000', 'FF0000' ]
, regionResolution: 6
, fitToCurveResolution: 7
})
.then( function( vectorFigures ) {

Convert the raster figures in vector representations.

Cut Outline

Cx.cutOutline$( figures, { offset: 10 })
.then( function( cutOutlineFigures ) {
// ...

Create a cut outline around a list of figures.

General

var f = Cx.findByType(figures,type);
var fs = Cx.filterByType(figures,type);

find and filter versions to work with types directly.

Cx.forEachText(figures, function(text){ ... }, context);
Cx.forEachShape(figures, function(shape){ ... }, context);
Cx.forEachRaster(figures, function(raster){ ... }, context);
Cx.forEachGroup(figures, function(group){ ... }, context);
Cx.forEachProxyGroup(figures, function(proxyGroup){ ... }, context);

Iteration by figure type.

var cs = Cx.cloneDeep(figures);

Configuration

Canvas API offers extensions and global parameters to configure the user experience in a per client basis. To set up your configuration options is to include a Cx.Canvas.Config.js file after Canvas scripts has been loaded (this may improve later when Canvas starts offering head.js based loading tools).

<!-- Load Canvas Scripts -->
...
<!-- End Canvas Scripts section -->
<script type="text/javascript" src="Cx.Canvas.Config.js"></script>

In this file, you will define the configuration of Canvas just as you would do with a Web.Config file in ASP.Net.

User defined typed metadata for figures

It is very useful to be able to attach metadata to each figure in a document when implementing the client logic. Things like “Fabric”, “Material” or “HighQualityPrinting” may only have meaning in the particular context of a certain application. The approach that Canvas takes, it to give clients the basic properties that are common when building graphic application and offer an extension point for user defined properties.

In you Cx.Canvas.Config.js file you can call the function:

Cx.Figure.setupClientProperties({
fabricType : { type: 'string', def: 'Cotton' }
, highQualityPrinting : { type: 'boolean', def: false, dropDef:true }
// , ... other properties
});

Once this is inplace your client can use their own defined properties in the same way it uses the normal figures properties (like “pin, “layer”, etc).

// In the client code
// getter
var ft = figure.fabricType() ;
// setter
figure.fabricType('Linen');

For each property, the type config option let the system do automatic parsing into the real representation of your properties. You can select ‘string’, ‘number’, ‘integer’, ‘boolean’, ‘point’, ‘color’, ‘brush’, etc. For a complete list read this non existent documentation (?). This is very useful to avoid writing code like parseInt( figure.charLimit() ) in your code base that can lead to subtle bugs.

The def config set up a default value for figures where the property has not been yet defined and dropDef controls if the value is going to be dropped from the CDL in the case it is the default. This is useful for metadata that has a clear default that will not change in the future. For example, the Text columnCount property is dropped if its value is 1.

You can check a real configuration file used by Kiosk4 here: Cx.Canvas.Config.js

Properties defined by one client and stored in Cx servers are not going to be dropped, even if other clients will open these documents. All the metadata will be kept safe as an internal key-value dictionary in the figures.

Because other clients may later use the same names, it is convenient to use some kind of namespace when naming your client defined properties. For TX project we are using properties with names like “txName”.

Global parameters

Canvas exposed several global parameters that control optimizations, general user interface related strategies and debugging modes. To change a particular variable use:

Cx.Global.configurationVariableName = enabled; // true-or-false

This is a (probably) incomplete list of configuration variables. Look directly in the source code to check an up-to-date version of it.

Name Def What it does
enableDocumentHistory true Document history is an expensive feature that simple applications should be able to disable. If you only care for quick rendering like in a Kiosk application and the UI is not going to offer undo/redo functionality, set this variable to false.
useCurvesBasedSelection true If the shapes are big enough, the user will be able to select a figure behind other figures frame. Disable this option to get pure frame based selection.
useCharactersBBoxBasedSelection true If the figure is a Text, avoid using the internal curves and perform the hit testing using the character bounding boxes. This allows us to take into account the pen width and offer the user big enough hit surfaces for the letters.
renderLocallyWhenPossible true For simple cases, when there are not TextureBrush, PowerClips or other unsuppoted CDL properties, the figure will be drawn locally using SVG and Raphael instead of calling the server RenderCachedFigure (Core: Figure/Render).
avoidCacheWhenRenderLocally true If we can draw locally, then there is no need to cache the figure.
renderPenBehindFillLocally true SVG does not support pens behind fill directly, so we have to produce the same effect by doing one path with the pen and another copy of the path with fill over it. Disable this option if the browser is having problems copying with the extra curves.
renderAnisotropicTransformLocally true SVG applies transforms to the pen nib. In chrome and firefox this can be disabled using the vector-effect property setted to non-scaling-stroke and compensate for the scaling factor manually. This flag enables local rendering for both browsers and leaves IE with server rendering.
processLocallyWhenPossible true For simple shapes we can avoid the call to Figure/Render directly because there is no processing needed to be done. This option improves a lot the experience when dealing with cliparts. The only caveat is that this figures will not have a cache Id so some idioms that the Designer2 was using needs to be updated to always call the new async function CadX.Figures.cache first.
showLocalPreviewRepresentation true When a server image representation is requested for the first time to the server a preview grayed local SVG based representation will be showed to the user as visual feedback.
showServerRenderingLoadingAnimation true When a server image representation is requested for the first time to the server and it is not possible to do a local preview (because the figure is a Raster or showLocalPreviewRepresentation is false) a loading animation is showed in the place the figure will appear.
previewUsingRealRepresentation true Brushes and pens are applied to the preview to match as best as possible the real representation while waiting for the server.
replaceColorsLocallyWhenPossible true When the figure can be drawn locally we can avoid re-processing it in the server for color replacing operations and directly replace the colors in the output figure maintained locally. This will invalidate the relationship between the current cached figure in the server and the local representation so the cache Id of the figure is dropped in the process.
useSubpixelPinForServerRendering true When asking an image to the server we only use a subpixel version of the pin. When the figure is translated an integer number of pixels (given the current zoom) we can use fastTranslate because we know that the image returned by the server will be exactly the same (because the subpixel pin is equal). This happens when users move things dragging it on the screen. Disabling this option will avoid the use of fastTranslate and always ask the server to render using the real pin. This is not something you will like to disable often, since the experience you will get is a lot worse.
computeFigureGeometryLocally true The bounding box and final transformation of the figures will be updated using the local object model instead of calling the server MeasureRenderInfo.
alwaysRenderLocally false This option will force the system to render everything locally even when the result is not perfect because of unsupported CDL properties. It is a good idea to use it when testing some things because of the speed improvements. This option overrides the renderLocallyWhenPossible option.

Debugging

If you add the cadx.debug=true switch in your App URL, Canvas will load debugging tools to aid in the development process.

Debugging Tools

CDL Editor

CDL Editor

Code Editor

Code Editor

Auto Selection

Whenever you add a figure to the active layer of a document, that figure
will get automatically selected for you in the Canvas when you commit$ your
transaction.

doc.add( figures );
doc.commit$(); // Triggers canvas.selectedFigures( figures );

If at any point of the transaction you change the selection by hand,
then auto selection will not kick in for newly selected figures. The
following transaction will leave Canvas without any selected figures.

doc.add([ figure, border ]);
doc.selectedFigures( figure );
doc.commit$(); // Manual selection, border is not selected

With this simple scheme, figure auto selection will do what is expected
for almost every transaction you will ever write. There are some corner
cases where you will not want want automatic selection to kick in for a
given transaction. If you find yourself in that case just call:

canvas.keepSelection();
doc.add(figure);
doc.commit$(); // Auto selection disabled for this transaction

You can call this function at any point before the transaction commit$.

If you want to disable the feature altogether for all your transactions
you can use:

canvas.autoSelection(false);

These is not recommended, because the app will need to select the newly
added figures by hand in each transaction.

Annotations

Figure annotations lets Apps highlight a figure including some text to give the user feedback. Annotations are Canvas dependent, you may spawn multiple Canvas over the same document showing different annotations in each of them.

The basic API is the following:

// Annotate figures with a given message and a highlight them with a box
canvas.annotate( , { text: 'Message', pen: pen });

This will annotate each of the figures with the text message over the highlight rectangle draw using the given pen.

The first parameter is actually quite flexible because there are different kind of scenarios where this feature will be useful.

// Annotate a single figure
canvas.annotate( figure, { ... } );
// Annotate multiple figures
canvas.annotate( figures, { ... } );
// You can also tell the system to only draw one annotation for the figures with
canvas.annotate( figures, { grouped: true, ... } );

These versions are useful, but normally applications will have some rules that defines when a figure should be annotated. The app could work the sync of the annotations after each commit but Canvas gives a tool for this common case that will simplify the work:

// Annotate every figure conditionally
canvas.annotate( function(figure){ ... return true if it should be annotated ... }, { ... } );
// Example, apply an annotation to each Text
canvas.annotate( function(figure){ return figure instanceof Cx.Text; }, { ... } );

You basically pass a filter function that will be used to dynamically find to which figures this annotation needs to be drawn. There is one shortcut in place if you just want to annotate every figure (also dynamically):

// Annotate all figures, same as function(figure){ return true; }
canvas.annotate( 'all', { ... } );

The basic parameters that you give to the annotation are:

Param Type Definition
text string Message to include next to the figure
pen Pen Pen used to stroke the highlight box
brush Brush Brush used to fill the highlight box, alpha is supported
color Color Message color if you want it to be different from the box color

Lets use a dynamic annotation to give feedback to the user about a probable date issue in its template:

// Annotate text that looks like date with "Not 2014?"
canvas.annotate( function(figure){
if( figure instanceof Cx.Text ){
var year = parseInt(figure.text(),10);
if( 2000 < year && year < 2014 )
return true;
}
return false;
},{
text: 'Not 2014?',
pen: Cx.Pen({color:Cx.Color({rgb:'F04111'})})
});
canvas.render$();

Here is what the user will see:

In this case the message is the same for all figures that pass the condition. But if you use dynamic annotations, you will normally want to set an individual message for each figure. Each of the annotation parameters accepts a function that takes a figure and returns the processed value. A good example, lets show the bounds of each figure to the user as an annotation:

// Annotate all figures with their bounds extents
canvas.annotate( 'all', {
pen: Cx.Pen({color:{rgb:'9A8CDB'}}),
text: function(figure){
var bounds = figure.bounds_();
return bounds.width().toFixed(0) + ' x ' +
bounds.height().toFixed(0) + ' mm';
}
});
canvas.render$();

Important The functions passed to annotate can safely use the sync version of the figure metrics ( figure.bounds_() ). Canvas will only produce the annotation when the figure has been properly updated. This will simplify a lot of use cases that involves metrics, like annotate to small or to big figures.

We can play with the box style, use dashed lines. Or only show the text. For example, if you do not pass any pen. The result will look like:

This looks really nice in this example, but it will be confusing if there are many overlapped figures. But I like the idea of having less noise so we can show annotations without the fear of covering everything with lines.

You have the option to use a different box style, using the boxStyle parameter. Only the default and ‘Clean’ is supported for the moment. The ‘Clean’ style only draws the corners of the box, so it looks like:

It is also possible to use overlapping annotations, the system will just place the messages on top of each other. But it is a good idea to avoid using to many annotations at the same time, and give the user some way to choose what he want to see.

If you want to remove the annotation, keep a reference to it and pass that to the Canvas unannotate function like:

var ann = canvas.annotate( figures, { ... } );
// later...
canvas.unannotate( ann );

You can also just clear all the annotations using:

canvas.clearAnnotations();

Conditions

Applications needs to be able to define the conditions under wich figures are able to be modified. These conditions are bussiness logic that is App dependent and can not be abstracted directly in Canvas. The Figure Conditions API fill this need providing a way for App to express rules that Canvas will use internally. At the same time, these conditions can be used by Apps to define their own logic. A condition query DSL is provided that makes it easy to define if a list of figures complies with a set of conditions.

App level conditions

Cx.Figure.condition defines or extends a condition:

Cx.Figure.condition(name,function(figure){ return boolean });
// Rasters will not be scalable for this App
Cx.Figure.condition('scalable',function(figure){
return ! ( figure instanceof Cx.Raster );
});

Once a condition is defined, it can be used to check if a list of figures fullfills the rule.

if( canvas.are(figures,'scalable') ){
// Good to go, allow this action...

Several conditions can be combined in single query

if( canvas.are(figures,'rotatable','scalable') ){
// Ok, transform the figures...

Because the figures will usually be the canvas.selectedFigures() list, you can just pass the target figures directly to the query:

if( canvas.are('selectedFigures','groupable') ){
// Green light, enable the group action buttons

All valid targets are accepted: 'figures', 'contextFigures', 'allFigures'.

Canvas driven by the App

Canvas uses conditions query checks internally in the Select Tool, the 3D Tool interaction, the HUD tools and actions and generally in every place that requires a decision that is better defined by the App.

The following conditions are checked: 'selectable', 'movable', 'scalable', 'rotatable', skewable', 'groupable', 'nodeEditable', 'zorderable'. For example, in 3D mode texture islands transformations are restricted and they are only movable and rotatable.

If the application extends any of these conditions, Canvas will correctly follow their rules.

// App defined decoration will not be selectable
Cx.Figure.condition('selectable',function(figure){
return figure.xType() != 'App.Decoration';
});

Helper conditions

Canvas pre-defines the following conditions: 'text', 'shape', 'raster', 'group', 'proxyGroup'.
Fine grained: 'simpleText', 'multiPartText', 'typesetText', 'fullColorRaster', 'monochromeRaster'.
For 3D mode: 'textureIsland'

These conditions can be used to simplify the condition definitions.

// Only text can be moved in this App
Cx.Figure.condition('movable','text');

The ! char can be used to negate a condition.

// Proxy Groups are not groupable in this App
Cx.Figure.condition('groupable','!proxyGroup');

Async Conditions

Async conditions can be specified post fixing the name with the $ char

// Defines what figures are small for this App
Cx.Figure.condition('small$',function(figure){
return figure.bounds$() .then(function(bounds){
return bounds.width() < 100 && bounds.height() < 100;
});
};

To check async conditions, the async version of the query is used

Cx.if$( canvas.are$( figures, '!small$' ), function(){
// Figures are big enough for this action to proceed...

Array Conditions

There are conditions that defines rules over the list of figures as a whole.

Cx.Figure.arrayCondition(name,function(figures){ return boolean });
// A list of figures is Groupable if there are at least two figures
Cx.Figure.arrayCondition('groupable',function(figures){
return figures.length >= 2;
});

Array conditions are combined with figure conditions when querying.

Cx.Figure.condition('groupable','!raster');
Cx.assert( canvas.are([ raster, text ],'groupable') == false );
// It passes the array condition, but fails because there is a raster

Canvas defines array conditions for 'groupable' and 'zorderable'.
It also defines helper conditions to check the number of figures: '1', '1+', '2', '2+'.

Cx.Figure.arrayCondition('groupable','2+');

Canvas Instance Conditions

It is advisable to use as much as possible App level figure conditions to define the rules of the application. If specific logic is needed for a given Canvas instance, the canvas version of the condition and arrayCondition functions can be used. This may be used to implement an interactive preview where figures can only be moved.

// None of the figures will be scalable in this Canvas
canvas.condition('scalable',function(figure){ return false; });
// Disallow z ordering if there is more than one figure in the list
canvas.arrayCondition('zorderable','1');

Canvas queries

Query to check if a list of figures fullfills a set of requirements

canvas.are(figures,conditions...);
canvas.are$(figures,conditions...);

Query to check if a single figure fullfills a set of requirements

canvas.is(figure,condition...);
canvas.is$(figure,conditions...);

Query to filter the figures that fullfills a set of requirements

canvas.only(figures,conditions...);
canvas.only$(figures,conditions...);

Save and Restore conditions

If the App can spawn several modes, each with its own bussines rules, common global conditions can be defined and then each mode can save at start up the current state of the conditions and restore them when the mode is changed (in the same way that HTML5 Canvas context.save() and canvas.restore() works).

Cx.Figure.saveConditions();
Cx.Figure.restoreConditions();

The same can be done with Canvas conditions, although normally it will be more convenient to instanciate a new Canvas for each mode.

canvas.saveConditions();
canvas.restoreConditions();

Array Like Interface

Canvas arrays needs to fire events when they are modified so others can sync with them and expose extra functions to simplify
their use: add, remove, replace. Canvas arrays are Array-like objects, all the standard array functions are exposed and you can use them as you will use normal array:

// Plain iteration
var figures = group.figures();
for( var n = 0; n < figures.length; n++ ) {
var f = figures[n];
// ...
}
// Lo-dash iteration
_.forEach(group.figures(),function(f){
// ...
});

Only the a[n] = o write interface needs to be avoided. Instead you need to use replaceAt or at.

// Wrong!
figures[2] = text;
// Good...
figures.replaceAt( 2, text );

The problem with using figures[2] = o, will just modify the 2 property of figures and nobody will know that this change has happened. For example, when you use replaceAt, there is code that deletes the old figure view and adds a new one (all this sync code is handled behind the scene). There is also another problem, if you set figures[6] in a 4 element array, the length should change but it will still be 6.

There are running standards to try to identify array like objects so it is easier to code utility libraries that are supposed to work with them. Underscore.js takes the approach of defining that if object.length is a positive number, then the object is array like. You will see checks in their code base like: obj.length === +obj.length.

Lo-dash and Undercore.js and other functional libraries do not modify the arrays you pass to them, they create new arrays with the results (_.map, _.filter, etc). Even things like _.shuffle. An array-like object then works fine on them, you can filter the arguments object for example, or a jQuery array.

Rationale

In Javascript we can not create a class that derives from an Array object, with different compromises. We played with that ideas but we couldn’t impose the compromises they propose to Canvas (the closest one).

jQuery actually uses something similar to this, at least in the final structure of their arrays.

var buttons = $('.button');

buttons is an Array Like object (like the ‘arguments’ function var). It has a length integer property, and their elements can be accessed using buttons[n] as a normal array. Because of this, every library that is coded to be able to handle javascript array like objects will work perfectly with them. You can use:

_.forEach( buttons, ... );

jQuery is not deriving from Array. Their array like object is actually just a normal object that has a length property and a ‘0’, ‘1’, ‘2’, etc property. Like this:

var jArray = { length: 3, '0': a, '1': b, '2': c };

Or equivalent:

var jArray = {};
jArray.length = 3;
jArray[0] = a;
jArray[1] = b;
jArray[2] = c;

So this works fine:

for( var n = 0; n < jArray.length; n++ ) {
var v = jArray[n];
// ...
}

Performance

Ecen if using an object {} instead of an array [] has performance implications, the best argument to show that it is not that bad is that jQuery arrays are actually created like this. There is a test page in jsPerf. You can look at the bar graphs, the three first are Array Like (assign, create and for each), the second three are Native Array. Some conclusions:

1) Chrome is awesome. Using plain objects is actually faster than native arrays.
2) Firefox also have better iteration and assign behavior in Array like objects.
3) IE is more consistent with ~2.5x performance penalty.

The test are using 1000 elements arrays, quite above the mean size of our own arrays (10 figures are big arrays in our system). So it seems that the price tag is quite low, given that the iteration cost almost disappear in the big picture of the computations Canvas or clients are performing.