mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Change the way context dependent font sizes are handled by delegating handling to view.
Closes #425
This commit is contained in:
parent
2b4ecfa669
commit
0aa73d90c5
10 changed files with 135 additions and 90 deletions
|
@ -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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue