2 * Copyright (C) 2008, 2009 Mihai Şucan
4 * This file is part of PaintWeb.
6 * PaintWeb is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * PaintWeb is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>.
19 * $URL: http://code.google.com/p/paintweb $
20 * $Date: 2009-08-27 20:30:01 +0300 $
24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
25 * @fileOverview Holds the text tool implementation.
28 // TODO: make this tool nicer to use.
31 * @class The text tool.
33 * @param {PaintWeb} app Reference to the main paint application object.
35 pwlib.tools.text = function (app) {
37 clearInterval = app.win.clearInterval,
38 config = app.config.text,
39 context = app.buffer.context,
44 MathRound = Math.round,
46 setInterval = app.win.setInterval;
49 * The interval ID used for invoking the drawing operation every few
53 * @see PaintWeb.config.toolDrawDelay
58 * Holds the previous tool ID.
63 var prevTool = app.tool ? app.tool._id : null;
66 * Tells if the drawing canvas needs to be updated or not.
72 var needsRedraw = false;
74 var inputString = null,
75 input_fontFamily = null,
76 ev_configChangeId = null,
77 ns_svg = "http://www.w3.org/2000/svg",
84 * Tool preactivation code. This method check if the browser has support for
85 * rendering text in Canvas.
87 * @returns {Boolean} True if the tool can be activated successfully, or false
90 this.preActivate = function () {
91 if (!gui.inputs.textString || !gui.inputs.text_fontFamily ||
92 !gui.elems.viewport) {
98 if (context.fillText && context.strokeText) {
102 // Opera can only render text via SVG Text.
103 // Note: support for Opera has been disabled.
104 // There are severe SVG redraw issues when updating the SVG text element.
105 // Besides, there are important memory leaks.
106 // Ultimately, there's a deal breaker: security violation. The SVG document
107 // which is rendered inside Canvas is considered "external"
108 // - get/putImageData() and toDataURL() stop working after drawImage(svg) is
110 /*if (pwlib.browser.opera) {
114 // Gecko 1.9.0 had its own proprietary Canvas 2D Text API.
115 if (context.mozPathText) {
119 alert(lang.errorTextUnsupported);
124 * The tool activation code. This sets up a few variables, starts the drawing
125 * timer and adds event listeners as needed.
127 this.activate = function () {
128 // Reset the mouse coordinates to the scroll top/left corner such that the
129 // text is rendered there.
130 mouse.x = Math.round(gui.elems.viewport.scrollLeft / image.canvasScale),
131 mouse.y = Math.round(gui.elems.viewport.scrollTop / image.canvasScale),
133 input_fontFamily = gui.inputs.text_fontFamily;
134 inputString = gui.inputs.textString;
136 if (!context.fillText && pwlib.browser.opera) {
137 ev_configChangeId = app.events.add('configChange', ev_configChange_opera);
138 inputString.addEventListener('input', ev_configChange_opera, false);
139 inputString.addEventListener('change', ev_configChange_opera, false);
141 ev_configChangeId = app.events.add('configChange', ev_configChange);
142 inputString.addEventListener('input', ev_configChange, false);
143 inputString.addEventListener('change', ev_configChange, false);
146 // Render text using the Canvas 2D context text API defined by HTML 5.
147 if (context.fillText && context.strokeText) {
148 _self.draw = _self.draw_spec;
150 } else if (pwlib.browser.opera) {
151 // Render text using a SVG Text element which is copied into Canvas using
153 _self.draw = _self.draw_opera;
156 } else if (context.mozPathText) {
157 // Render text using proprietary API available in Gecko 1.9.0.
158 _self.draw = _self.draw_moz;
159 textWidth = context.mozMeasureText(inputString.value);
163 timer = setInterval(_self.draw, app.config.toolDrawDelay);
169 * The tool deactivation simply consists of removing the event listeners added
170 * when the tool was constructed, and clearing the buffer canvas.
172 this.deactivate = function () {
174 clearInterval(timer);
179 if (ev_configChangeId) {
180 app.events.remove('configChange', ev_configChangeId);
183 if (!context.fillText && pwlib.browser.opera) {
184 inputString.removeEventListener('input', ev_configChange_opera, false);
185 inputString.removeEventListener('change', ev_configChange_opera, false);
187 inputString.removeEventListener('input', ev_configChange, false);
188 inputString.removeEventListener('change', ev_configChange, false);
194 context.clearRect(0, 0, image.width, image.height);
200 * Initialize the SVG document for Opera. This is used for rendering the text.
203 function initOpera () {
204 svgDoc = doc.createElementNS(ns_svg, 'svg');
205 svgDoc.setAttributeNS(ns_svg, 'version', '1.1');
207 svgText = doc.createElementNS(ns_svg, 'text');
208 svgText.appendChild(doc.createTextNode(inputString.value));
209 svgDoc.appendChild(svgText);
211 svgText.style.font = context.font;
213 if (app.config.shapeType !== 'stroke') {
214 svgText.style.fill = context.fillStyle;
216 svgText.style.fill = 'none';
219 if (app.config.shapeType !== 'fill') {
220 svgText.style.stroke = context.strokeStyle;
221 svgText.style.strokeWidth = context.lineWidth;
223 svgText.style.stroke = 'none';
224 svgText.style.strokeWidth = context.lineWidth;
227 textWidth = svgText.getComputedTextLength();
228 textHeight = svgText.getBBox().height;
230 svgDoc.setAttributeNS(ns_svg, 'width', textWidth);
231 svgDoc.setAttributeNS(ns_svg, 'height', textHeight + 10);
232 svgText.setAttributeNS(ns_svg, 'x', 0);
233 svgText.setAttributeNS(ns_svg, 'y', textHeight);
237 * The <code>configChange</code> application event handler. This is also the
238 * <code>input</code> and <code>change</code> event handler for the text
239 * string input element. This method updates the Canvas text-related
240 * properties as needed, and re-renders the text.
242 * <p>This function is not used on Opera.
244 * @param {Event|pwlib.appEvent.configChange} ev The application/DOM event
247 function ev_configChange (ev) {
248 if (ev.type === 'input' || ev.type === 'change' ||
249 (!ev.group && ev.config === 'shapeType') ||
250 (ev.group === 'line' && ev.config === 'lineWidth')) {
253 // Update the text width.
254 if (!context.fillText && context.mozMeasureText) {
255 textWidth = context.mozMeasureText(inputString.value);
260 if (ev.type !== 'configChange' && ev.group !== 'text') {
268 if (ev.value === '+') {
280 font += config.fontSize + 'px ' + config.fontFamily;
283 if ('mozTextStyle' in context) {
284 context.mozTextStyle = font;
292 // Update the text width.
293 if (ev.config !== 'textAlign' && ev.config !== 'textBaseline' &&
294 !context.fillText && context.mozMeasureText) {
295 textWidth = context.mozMeasureText(inputString.value);
300 * The <code>configChange</code> application event handler. This is also the
301 * <code>input</code> and <code>change</code> event handler for the text
302 * string input element. This method updates the Canvas text-related
303 * properties as needed, and re-renders the text.
305 * <p>This is function is specific to Opera.
307 * @param {Event|pwlib.appEvent.configChange} ev The application/DOM event
310 function ev_configChange_opera (ev) {
311 if (ev.type === 'input' || ev.type === 'change') {
312 svgText.replaceChild(doc.createTextNode(this.value), svgText.firstChild);
316 if (!ev.group && ev.config === 'shapeType') {
317 if (ev.value !== 'stroke') {
318 svgText.style.fill = context.fillStyle;
320 svgText.style.fill = 'none';
323 if (ev.value !== 'fill') {
324 svgText.style.stroke = context.strokeStyle;
325 svgText.style.strokeWidth = context.lineWidth;
327 svgText.style.stroke = 'none';
328 svgText.style.strokeWidth = context.lineWidth;
333 if (!ev.group && ev.config === 'fillStyle') {
334 if (app.config.shapeType !== 'stroke') {
335 svgText.style.fill = ev.value;
340 if ((!ev.group && ev.config === 'strokeStyle') ||
341 (ev.group === 'line' && ev.config === 'lineWidth')) {
342 if (app.config.shapeType !== 'fill') {
343 svgText.style.stroke = context.strokeStyle;
344 svgText.style.strokeWidth = context.lineWidth;
349 if (ev.type === 'configChange' && ev.group === 'text') {
353 if (ev.value === '+') {
365 font += config.fontSize + 'px ' + config.fontFamily;
367 svgText.style.font = font;
375 textWidth = svgText.getComputedTextLength();
376 textHeight = svgText.getBBox().height;
378 svgDoc.setAttributeNS(ns_svg, 'width', textWidth);
379 svgDoc.setAttributeNS(ns_svg, 'height', textHeight + 10);
380 svgText.setAttributeNS(ns_svg, 'x', 0);
381 svgText.setAttributeNS(ns_svg, 'y', textHeight);
385 * Add a new font family into the font family drop down. This function is
386 * invoked by the <code>ev_configChange()</code> function when the user
387 * attempts to add a new font family.
391 * @param {pwlib.appEvent.configChange} ev The application event object.
393 function fontFamilyAdd (ev) {
394 var new_font = prompt(lang.promptTextFont) || '';
395 new_font = new_font.replace(/^\s+/, '').replace(/\s+$/, '') ||
398 // Check if the font name is already in the list.
399 var opt, new_font2 = new_font.toLowerCase(),
400 n = input_fontFamily.options.length;
402 for (var i = 0; i < n; i++) {
403 opt = input_fontFamily.options[i];
404 if (opt.value.toLowerCase() == new_font2) {
405 config.fontFamily = opt.value;
406 input_fontFamily.selectedIndex = i;
407 input_fontFamily.value = config.fontFamily;
408 ev.value = config.fontFamily;
415 opt = doc.createElement('option');
416 opt.value = new_font;
417 opt.appendChild(doc.createTextNode(new_font));
418 input_fontFamily.insertBefore(opt, input_fontFamily.options[n-1]);
419 input_fontFamily.selectedIndex = n-1;
420 input_fontFamily.value = new_font;
422 config.fontFamily = new_font;
426 * The <code>mousemove</code> event handler.
428 this.mousemove = function () {
433 * Perform the drawing operation using standard 2D context methods.
435 * @see PaintWeb.config.toolDrawDelay
437 this.draw_spec = function () {
442 context.clearRect(0, 0, image.width, image.height);
444 if (app.config.shapeType != 'stroke') {
445 context.fillText(inputString.value, mouse.x, mouse.y);
448 if (app.config.shapeType != 'fill') {
450 context.strokeText(inputString.value, mouse.x, mouse.y);
458 * Perform the drawing operation in Gecko 1.9.0.
460 this.draw_moz = function () {
465 context.clearRect(0, 0, image.width, image.height);
470 if (config.textAlign === 'center') {
471 x -= MathRound(textWidth / 2);
472 } else if (config.textAlign === 'right') {
476 if (config.textBaseline === 'top') {
477 y += config.fontSize;
478 } else if (config.textBaseline === 'middle') {
479 y += MathRound(config.fontSize / 2);
482 context.setTransform(1, 0, 0, 1, x, y);
484 context.mozPathText(inputString.value);
486 if (app.config.shapeType != 'stroke') {
490 if (app.config.shapeType != 'fill') {
494 context.setTransform(1, 0, 0, 1, 0, 0);
500 * Perform the drawing operation in Opera using SVG.
502 this.draw_opera = function () {
507 context.clearRect(0, 0, image.width, image.height);
512 if (config.textAlign === 'center') {
513 x -= MathRound(textWidth / 2);
514 } else if (config.textAlign === 'right') {
518 if (config.textBaseline === 'bottom') {
520 } else if (config.textBaseline === 'middle') {
521 y -= MathRound(textHeight / 2);
524 context.drawImage(svgDoc, x, y);
530 * The <code>click</code> event handler. This method completes the drawing
531 * operation by inserting the text into the layer canvas.
533 this.click = function () {
539 * The <code>keydown</code> event handler allows users to press the
540 * <kbd>Escape</kbd> key to cancel the drawing operation and return to the
543 * @param {Event} ev The DOM Event object.
544 * @returns {Boolean} True if the key was recognized, or false if not.
546 this.keydown = function (ev) {
547 if (!prevTool || ev.kid_ != 'Escape') {
551 mouse.buttonDown = false;
552 app.toolActivate(prevTool, ev);
558 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: