Change the way context dependent font sizes are handled by delegating handling to view.

Closes #425
This commit is contained in:
Jürg Lehni 2014-03-17 16:41:57 +01:00
parent 2b4ecfa669
commit 0aa73d90c5
10 changed files with 135 additions and 90 deletions

View file

@ -118,7 +118,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
* @bean
*/
getView: function() {
return this.project && this.project.view;
return this.project && this.project.getView();
},
/**

View file

@ -103,7 +103,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
if (point)
matrix.translate(point);
matrix._owner = this;
this._style = new Style(project._currentStyle, this);
this._style = new Style(project._currentStyle, this, project);
// If _project is already set, the item was already moved into the DOM
// hierarchy. Used by Layer, where it's added to project.layers instead
if (!this._project) {
@ -154,7 +154,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
install: function(type) {
// If the view requires counting of installed mouse events,
// increase the counters now according to mouseFlags
var counters = this._project.view._eventCounters;
var counters = this.getView()._eventCounters;
if (counters) {
for (var key in mouseFlags) {
counters[key] = (counters[key] || 0)
@ -165,7 +165,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
uninstall: function(type) {
// If the view requires counting of installed mouse events,
// decrease the counters now according to mouseFlags
var counters = this._project.view._eventCounters;
var counters = this.getView()._eventCounters;
if (counters) {
for (var key in mouseFlags)
counters[key] -= mouseFlags[key][type] || 0;
@ -194,7 +194,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
},
_animateItem: function(animate) {
this._project.view._animateItem(this, animate);
this.getView()._animateItem(this, animate);
},
_serialize: function(options, dictionary) {
@ -1225,6 +1225,15 @@ var Item = Base.extend(Callback, /** @lends Item# */{
this._installEvents(true);
},
/**
* The view that this item belongs to.
* @type View
* @bean
*/
getView: function() {
return this._project.getView();
},
/**
* Overrides Callback#_installEvents to also call _installEvents on all
* children.
@ -1566,8 +1575,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
*/
rasterize: function(resolution) {
var bounds = this.getStrokeBounds(),
view = this._project.view,
scale = (resolution || view && view.getResolution() || 72) / 72,
scale = (resolution || this.getView().getResolution()) / 72,
// Floor top-left corner and ceil bottom-right corner, to never
// blur or cut pixels.
topLeft = bounds.getTopLeft().floor(),
@ -1686,7 +1694,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// chain already to determine the rough bounds.
var matrix = this._matrix,
parentTotalMatrix = options._totalMatrix,
view = this._project.view,
view = this.getView(),
// Keep the accumulated matrices up to this item in options, so we
// can keep calculating the correct _tolerancePadding values.
totalMatrix = options._totalMatrix = parentTotalMatrix
@ -1694,7 +1702,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// If this is the first one in the recursion, factor in the
// zoom of the view and the globalMatrix of the item.
: this.getGlobalMatrix().clone().preConcatenate(
view ? view._matrix : new Matrix()),
view._matrix),
// Calculate the transformed padding as 2D size that describes the
// transformed tolerance circle / ellipse. Make sure it's never 0
// since we're using it for division.

View file

@ -298,13 +298,13 @@ var Raster = Item.extend(/** @lends Raster# */{
image;
function loaded() {
var view = that._project.view;
if (view)
var view = that.getView();
if (view) {
paper = view._scope;
that.setImage(image);
that.fire('load');
if (view)
that.setImage(image);
that.fire('load');
view.update();
}
}
/*#*/ if (__options.environment == 'browser') {

View file

@ -43,20 +43,23 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* Note that when working with PaperScript, a project is automatically
* created for us and the {@link PaperScope#project} variable points to it.
*
* @param {View|HTMLCanvasElement} view Either a view object or an HTML
* Canvas element that should be wrapped in a newly created view.
* @param {HTMLCanvasElement} element an HTML anvas element that should be
* used as the element for the view.
*/
initialize: function Project(view) {
initialize: function Project(element) {
// Activate straight away by passing true to PaperScopeItem constructor,
// so paper.project is set, as required by Layer and DoumentView
// constructors.
PaperScopeItem.call(this, true);
this.layers = [];
this.symbols = [];
this._currentStyle = new Style();
this._currentStyle = new Style(null, null, this);
this.activeLayer = new Layer();
if (view)
this.view = view instanceof View ? view : View.create(view);
// If no view is provided, we create a 1x1 px canvas view just so we
// have something to do size calculations with.
// (e.g. PointText#_getBounds)
this._view = View.create(this,
element || CanvasProvider.getCanvas(1, 1));
this._selectedItems = {};
this._selectedItemCount = 0;
// See Item#draw() for an explanation of _updateVersion
@ -110,8 +113,8 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
remove: function remove() {
if (!remove.base.call(this))
return false;
if (this.view)
this.view.remove();
if (this._view)
this._view.remove();
return true;
},
@ -119,7 +122,11 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* The reference to the project's view.
* @name Project#view
* @type View
* @bean
*/
getView: function() {
return this._view;
},
/**
* The currently active path style. All selected items and newly

View file

@ -136,10 +136,11 @@ var Style = Base.extend(new function() {
// - Color values are not stored as converted colors immediately. The
// raw value is stored, and conversion only happens in the getter.
fields[set] = function(value) {
var children = this._item && this._item._children;
var owner = this._owner,
children = owner && owner._children;
// Only unify styles on children of Groups, excluding CompoundPaths.
if (children && children.length > 0
&& this._item._type !== 'compound-path') {
&& !(owner instanceof CompoundPath)) {
for (var i = 0, l = children.length; i < l; i++)
children[i]._style[set](value);
} else {
@ -154,28 +155,29 @@ var Style = Base.extend(new function() {
// converted and cloned in the getter further down.
if (value._owner)
value = value.clone();
value._owner = this._item;
value._owner = owner;
}
}
// Note: We do not convert the values to Colors in the
// setter. This only happens once the getter is called.
this._values[key] = value;
// Notify the item of the style change STYLE is always set,
// Notify the owner of the style change STYLE is always set,
// additional flags come from flags, as used for STROKE:
if (this._item)
this._item._changed(flag || /*#=*/ Change.STYLE);
if (owner)
owner._changed(flag || /*#=*/ Change.STYLE);
}
}
};
fields[get] = function(_dontMerge) {
var value,
children = this._item && this._item._children;
// If this item has children, walk through all of them and see if
var owner = this._owner,
children = owner && owner._children,
value;
// If the owner has children, walk through all of them and see if
// they all have the same style.
// If true is passed for _dontMerge, don't merge children styles
if (!children || children.length === 0 || _dontMerge
|| this._item instanceof CompoundPath) {
|| owner instanceof CompoundPath) {
var value = this._values[key];
if (value === undefined) {
value = this._defaults[key];
@ -187,7 +189,7 @@ var Style = Base.extend(new function() {
this._values[key] = value = Color.read([value], 0,
{ readNull: true, clone: true });
if (value)
value._owner = this._item;
value._owner = owner;
}
return value;
}
@ -196,7 +198,7 @@ var Style = Base.extend(new function() {
if (i === 0) {
value = childValue;
} else if (!Base.equals(value, childValue)) {
// If there is another item with a different
// If there is another child with a different
// style, the style is not defined:
return undefined;
}
@ -220,11 +222,12 @@ var Style = Base.extend(new function() {
}, /** @lends Style# */{
_class: 'Style',
initialize: function Style(style, _item) {
initialize: function Style(style, _owner, _project) {
// We keep values in a separate object that we can iterate over.
this._values = {};
this._item = _item;
if (_item instanceof TextItem)
this._owner = _owner;
this._project = _owner && _owner._project || _project || paper.project;
if (_owner instanceof TextItem)
this._defaults = this._textDefaults;
if (style)
this.set(style);
@ -269,17 +272,27 @@ var Style = Base.extend(new function() {
return !!this.getShadowColor() && this.getShadowBlur() > 0;
},
/**
* The view that this style belongs to.
* @type View
* @bean
*/
getView: function() {
return this._project.getView();
},
// Overrides
getFontStyle: function() {
var size = this.getFontSize();
var fontSize = this.getFontSize();
// To prevent an obscure iOS 7 crash, we have to convert the size to a
// string first before passing it to the regular expression.
// This nonsensical statement would also prevent the bug, prooving that
// the issue is not the regular expression itself, but something deeper
// down in the optimizer: if (size === 0) size = 0;
// The following nonsensical statement would also prevent the bug,
// prooving that the issue is not the regular expression itself, but
// something deeper down in the optimizer:
// `if (size === 0) size = 0;`
return this.getFontWeight()
+ ' ' + size + (/[a-z]/i.test(size + '') ? ' ' : 'px ')
+ ' ' + fontSize + (/\w/i.test(fontSize + '') ? ' ' : 'px ')
+ this.getFontFamily();
},
@ -297,8 +310,11 @@ var Style = Base.extend(new function() {
getLeading: function getLeading() {
// Override leading to return fontSize * 1.2 by default.
var leading = getLeading.base.call(this);
return leading != null ? leading : this.getFontSize() * 1.2;
var leading = getLeading.base.call(this),
fontSize = this.getFontSize();
if (/pt|em|%|px/.test(fontSize))
fontSize = this.getView().getPixelSize(fontSize);
return leading != null ? leading : fontSize * 1.2;
}
// DOCS: why isn't the example code showing up?

View file

@ -404,7 +404,7 @@ new function() {
exportSVG: function(options) {
options = setOptions(options);
var layers = this.layers,
size = this.view.getSize(),
size = this.getView().getSize(),
node = createElement('svg', {
x: 0,
y: 0,

View file

@ -531,7 +531,7 @@ new function() {
paper = scope;
var item = importSVG(svg, isRoot, options),
onLoad = options.onLoad,
view = scope.project && scope.project.view;
view = scope.project && scope.getView();
if (onLoad)
onLoad.call(this, item);
view.update();

View file

@ -88,7 +88,6 @@ var PointText = TextItem.extend(/** @lends PointText# */{
lines = this._lines,
leading = style.getLeading(),
shadowColor = ctx.shadowColor;
ctx.font = style.getFontStyle();
ctx.textAlign = style.getJustification();
for (var i = 0, l = lines.length; i < l; i++) {
@ -103,36 +102,24 @@ var PointText = TextItem.extend(/** @lends PointText# */{
ctx.strokeText(line, 0, 0);
ctx.translate(0, leading);
}
}
}, new function() {
var measureCtx = null;
},
return {
_getBounds: function(getter, matrix) {
// Create an in-memory canvas on which to do the measuring
if (!measureCtx)
measureCtx = CanvasProvider.getContext(1, 1);
var style = this._style,
lines = this._lines,
count = lines.length,
justification = style.getJustification(),
leading = style.getLeading(),
x = 0;
// Measure the real width of the text. Unfortunately, there is no
// sane way to measure text height with canvas
measureCtx.font = style.getFontStyle();
var width = 0;
for (var i = 0; i < count; i++)
width = Math.max(width, measureCtx.measureText(lines[i]).width);
// Adjust for different justifications
if (justification !== 'left')
x -= width / (justification === 'center' ? 2: 1);
// Until we don't have baseline measuring, assume 1 / 4 leading as a
// rough guess:
var bounds = new Rectangle(x,
count ? - 0.75 * leading : 0,
width, count * leading);
return matrix ? matrix._transformBounds(bounds, bounds) : bounds;
}
};
_getBounds: function(getter, matrix) {
var style = this._style,
lines = this._lines,
numLines = lines.length,
justification = style.getJustification(),
leading = style.getLeading(),
width = this.getView().getTextWidth(style.getFontStyle(), lines),
x = 0;
// Adjust for different justifications.
if (justification !== 'left')
x -= width / (justification === 'center' ? 2: 1);
// Until we don't have baseline measuring, assume 1 / 4 leading as a
// rough guess:
var bounds = new Rectangle(x,
numLines ? - 0.75 * leading : 0,
width, numLines * leading);
return matrix ? matrix._transformBounds(bounds, bounds) : bounds;
}
});

View file

@ -31,7 +31,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
* @name CanvasView#initialize
* @param {Size} size the size of the canvas to be created
*/
initialize: function CanvasView(canvas) {
initialize: function CanvasView(project, canvas) {
// Handle canvas argument
if (!(canvas instanceof HTMLCanvasElement)) {
// See if the arguments describe the view size:
@ -56,7 +56,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
this._pixelRatio = deviceRatio / backingStoreRatio;
}
/*#*/ } // __options.environment == 'browser'
View.call(this, canvas);
View.call(this, project, canvas);
},
_setViewSize: function(size) {
@ -77,13 +77,40 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
}
},
/**
* Converts the provide size in any of the units allowed in the browser to
* pixels, by the use of the context.font property.
*/
getPixelSize: function(size) {
var ctx = this._context,
prevFont = ctx.font;
ctx.font = size + ' serif';
size = parseFloat(ctx.font);
ctx.font = prevFont;
return size;
},
getTextWidth: function(font, lines) {
var ctx = this._context,
prevFont = ctx.font,
width = 0;
ctx.font = font;
// Measure the real width of the text. Unfortunately, there is no sane
// way to measure text height with canvas.
for (var i = 0, l = lines.length; i < l; i++)
width = Math.max(width, ctx.measureText(lines[i]).width);
ctx.font = prevFont;
return width;
},
/**
* Updates the view if there are changes.
*
* @function
*/
update: function() {
if (!this._project._needsUpdate)
var project = this._project;
if (!project || !project._needsUpdate)
return false;
// Initial tests conclude that clearing the canvas using clearRect
// is always faster than setting canvas.width = canvas.width
@ -91,8 +118,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
var ctx = this._context,
size = this._viewSize;
ctx.clearRect(0, 0, size.width + 1, size.height + 1);
this._project.draw(ctx, this._matrix, this._pixelRatio);
this._project._needsUpdate = false;
project.draw(ctx, this._matrix, this._pixelRatio);
project._needsUpdate = false;
return true;
}
}, new function() { // Item based mouse handling:

View file

@ -22,11 +22,11 @@
var View = Base.extend(Callback, /** @lends View# */{
_class: 'View',
initialize: function View(element) {
initialize: function View(project, element) {
// Store reference to the currently active global paper scope, and the
// active project, which will be represented by this view
this._scope = paper;
this._project = paper.project;
this._project = project;
this._scope = project._scope;
this._element = element;
var size;
/*#*/ if (__options.environment == 'browser') {
@ -139,8 +139,8 @@ var View = Base.extend(Callback, /** @lends View# */{
View._views.splice(View._views.indexOf(this), 1);
delete View._viewsById[this._id];
// Unlink from project
if (this._project.view == this)
this._project.view = null;
if (this._project._view === this)
this._project._view = null;
/*#*/ if (__options.environment == 'browser') {
// Uninstall event handlers again for this view.
DomEvent.remove(this._element, this._viewEvents);
@ -658,14 +658,14 @@ var View = Base.extend(Callback, /** @lends View# */{
_viewsById: {},
_id: 0,
create: function(element) {
create: function(project, element) {
/*#*/ if (__options.environment == 'browser') {
if (typeof element === 'string')
element = document.getElementById(element);
/*#*/ } // __options.environment == 'browser'
// Factory to provide the right View subclass for a given element.
// Produces only CanvasViews for now:
return new CanvasView(element);
return new CanvasView(project, element);
}
}
}, new function() {