author | David Anderson <danderson@mozilla.com> |
Fri, 06 Jan 2012 17:03:12 -0800 | |
changeset 105523 | 266b7ffc925d7b04bd7ecca549dde55047758676 |
parent 105521 | 2f73f3274b267adb5fd6447ddb640b31fc930189 (current diff) |
parent 83948 | 5a446202be5fa2633e4cc1c6113a6ebded512882 (diff) |
child 105524 | df3fab333dbc7011bc12816f1cf3d521107465bf |
push id | 23447 |
push user | danderson@mozilla.com |
push date | Tue, 11 Sep 2012 17:34:27 +0000 |
treeherder | autoland@fdfaef738a00 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 12.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/.gitignore +++ b/.gitignore @@ -18,17 +18,17 @@ ID /configure /config.cache /config.log # Empty marker file that's generated when we check out NSS security/manager/.nss.checkout # Build directories -obj/* +obj*/ # Build directories for js shell */_DBG.OBJ/ */_OPT.OBJ/ # SpiderMonkey configury js/src/configure js/src/autom4te.cache
--- a/accessible/tests/mochitest/events/test_scroll.xul +++ b/accessible/tests/mochitest/events/test_scroll.xul @@ -27,17 +27,17 @@ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/> <script type="application/javascript"> <![CDATA[ //////////////////////////////////////////////////////////////////////////// - // Hack to make xul:tabbrowser work + // Hacks to make xul:tabbrowser work const Ci = Components.interfaces; const CC = Components.classes; Components.utils.import("resource://gre/modules/Services.jsm"); var handleDroppedLink = null; @@ -48,16 +48,18 @@ }; var gURLBar = { focused: false }; var gFindBarInitialized = false; + function goSetCommandEnabled() {} + //////////////////////////////////////////////////////////////////////////// // Tests function getTabDocument() { return getNode("tabBrowser").selectedBrowser.contentDocument; }
--- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -1,8 +1,10 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * @@ -55,18 +57,18 @@ XPCOMUtils.defineLazyGetter(Services, 's }); XPCOMUtils.defineLazyGetter(Services, 'fm', function() { return Cc['@mozilla.org/focus-manager;1'] .getService(Ci.nsIFocusManager); }); // In order to use http:// scheme instead of file:// scheme // (that is much more restricted) the following code kick-off -// a local http server listening on http://127.0.0.1:8888 and -// http://localhost:8888. +// a local http server listening on http://127.0.0.1:7777 and +// http://localhost:7777. function startupHttpd(baseDir, port) { const httpdURL = 'chrome://browser/content/httpd.js'; let httpd = {}; Services.scriptloader.loadSubScript(httpdURL, httpd); let server = new httpd.nsHttpServer(); server.registerDirectory('/', new LocalFile(baseDir)); server.registerContentType('appcache', 'text/cache-manifest'); server.start(port); @@ -86,16 +88,19 @@ function addPermissions(urls) { permissions.forEach(function(permission) { Services.perms.add(uri, permission, allow); }); }); } var shell = { + // FIXME/bug 678695: this should be a system setting + preferredScreenBrightness: 1.0, + get home() { delete this.home; return this.home = document.getElementById('homescreen'); }, get homeURL() { try { let homeSrc = Services.env.get('B2G_HOMESCREEN'); @@ -134,17 +139,17 @@ var shell = { let fileScheme = 'file://'; if (homeURL.substring(0, fileScheme.length) == fileScheme) { homeURL = homeURL.replace(fileScheme, ''); let baseDir = homeURL.split('/'); baseDir.pop(); baseDir = baseDir.join('/'); - const SERVER_PORT = 8888; + const SERVER_PORT = 6666; startupHttpd(baseDir, SERVER_PORT); let baseHost = 'http://localhost'; homeURL = homeURL.replace(baseDir, baseHost + ':' + SERVER_PORT); } addPermissions([homeURL]); } catch (e) { let msg = 'Fatal error during startup: [' + e + '[' + homeURL + ']'; @@ -190,27 +195,28 @@ var shell = { handleEvent: function shell_handleEvent(evt) { switch (evt.type) { case 'keypress': switch (evt.keyCode) { case evt.DOM_VK_HOME: this.sendEvent(this.home.contentWindow, 'home'); break; case evt.DOM_VK_SLEEP: - screen.mozEnabled = !screen.mozEnabled; + this.toggleScreen(); break; case evt.DOM_VK_ESCAPE: if (evt.defaultPrevented) return; this.doCommand('cmd_close'); break; } break; case 'load': this.home.removeEventListener('load', this, true); + this.turnScreenOn(); this.sendEvent(window, 'ContentStart'); break; case 'MozApplicationManifest': try { if (!Services.prefs.getBoolPref('browser.cache.offline.enable')) return; let contentWindow = evt.originalTarget.defaultView; @@ -243,17 +249,31 @@ var shell = { } break; } }, sendEvent: function shell_sendEvent(content, type, details) { let event = content.document.createEvent('CustomEvent'); event.initCustomEvent(type, true, true, details ? details : {}); content.dispatchEvent(event); - } + }, + toggleScreen: function shell_toggleScreen() { + if (screen.mozEnabled) + this.turnScreenOff(); + else + this.turnScreenOn(); + }, + turnScreenOff: function shell_turnScreenOff() { + screen.mozEnabled = false; + screen.mozBrightness = 0.0; + }, + turnScreenOn: function shell_turnScreenOn() { + screen.mozEnabled = true; + screen.mozBrightness = this.preferredScreenBrightness; + }, }; (function VirtualKeyboardManager() { let activeElement = null; let isKeyboardOpened = false; let constructor = { handleEvent: function vkm_handleEvent(evt) {
--- a/b2g/chrome/content/shell.xul +++ b/b2g/chrome/content/shell.xul @@ -35,16 +35,17 @@ - the provisions above, a recipient may use your version of this file under - the terms of any one of the MPL, the GPL or the LGPL. - - ***** END LICENSE BLOCK ***** --> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="shell" width="480" height="800" + windowtype="navigator:browser" #ifdef ANDROID sizemode="fullscreen" #endif style="background: black; overflow: hidden;" onload="shell.start();" onunload="shell.stop();"> <script type="application/javascript" src="chrome://browser/content/commandUtil.js"/>
--- a/browser/branding/nightly/pref/firefox-branding.js +++ b/browser/branding/nightly/pref/firefox-branding.js @@ -1,17 +1,17 @@ pref("startup.homepage_override_url","http://www.mozilla.org/projects/%APP%/%VERSION%/whatsnew/"); pref("startup.homepage_welcome_url","http://www.mozilla.org/projects/%APP%/%VERSION%/firstrun/"); // The time interval between checks for a new version (in seconds) -pref("app.update.interval", 7200); // 2 hours +pref("app.update.interval", 3600); // 1 hour // The time interval between the downloading of mar file chunks in the // background (in seconds) pref("app.update.download.backgroundInterval", 60); -// Give the user x seconds to react before showing the big UI. default=12 hours -pref("app.update.promptWaitTime", 43200); +// Give the user x seconds to react before showing the big UI. default=1 hour +pref("app.update.promptWaitTime", 3600); // URL user can browse to manually if for some reason all update installation // attempts fail. pref("app.update.url.manual", "http://nightly.mozilla.org/"); // A default value for the "More information about this update" link // supplied in the "An update is available" page of the update wizard. pref("app.update.url.details", "http://www.mozilla.org/projects/%APP%/"); // Release notes and vendor URLs
--- a/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain.js +++ b/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain.js @@ -37,13 +37,18 @@ * * ***** END LICENSE BLOCK ***** */ /** * Test added with bug 460086 to test the behavior of the new API that was added * to remove all traces of visiting a site. */ +Components.utils.import("resource://gre/modules/Services.jsm"); + function run_test() { PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing-wrapper;1"; load("do_test_removeDataFromDomain.js"); do_test(); + + // Shutdown the download manager. + Services.obs.notifyObservers(null, "quit-application", null); }
--- a/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain_activeDownloads.js +++ b/browser/components/privatebrowsing/test/unit/test_privatebrowsingwrapper_removeDataFromDomain_activeDownloads.js @@ -37,13 +37,18 @@ * * ***** END LICENSE BLOCK ***** */ /** * Test added with bug 460086 to test the behavior of the new API that was added * to remove all traces of visiting a site. */ +Components.utils.import("resource://gre/modules/Services.jsm"); + function run_test() { PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1"; load("do_test_removeDataFromDomain_activeDownloads.js"); do_test(); + + // Shutdown the download manager. + Services.obs.notifyObservers(null, "quit-application", null); }
--- a/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain.js +++ b/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain.js @@ -37,13 +37,18 @@ * * ***** END LICENSE BLOCK ***** */ /** * Test added with bug 460086 to test the behavior of the new API that was added * to remove all traces of visiting a site. */ +Components.utils.import("resource://gre/modules/Services.jsm"); + function run_test() { PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1"; load("do_test_removeDataFromDomain.js"); do_test(); + + // Shutdown the download manager. + Services.obs.notifyObservers(null, "quit-application", null); }
--- a/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain_activeDownloads.js +++ b/browser/components/privatebrowsing/test/unit/test_removeDataFromDomain_activeDownloads.js @@ -37,13 +37,18 @@ * * ***** END LICENSE BLOCK ***** */ /** * Test added with bug 460086 to test the behavior of the new API that was added * to remove all traces of visiting a site. */ +Components.utils.import("resource://gre/modules/Services.jsm"); + function run_test() { PRIVATEBROWSING_CONTRACT_ID = "@mozilla.org/privatebrowsing;1"; load("do_test_removeDataFromDomain_activeDownloads.js"); do_test(); + + // Shutdown the download manager. + Services.obs.notifyObservers(null, "quit-application", null); }
--- a/browser/themes/winstripe/browser-aero.css +++ b/browser/themes/winstripe/browser-aero.css @@ -32,17 +32,17 @@ -moz-margin-start: 1px; } .panel-promo-message { font-style: italic; } } -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #navigator-toolbox > toolbar:not(:-moz-lwtheme), #browser-bottombox:not(:-moz-lwtheme) { background-color: @customToolbarColor@; } .tabbrowser-tab:not(:-moz-lwtheme), .tabs-newtab-button:not(:-moz-lwtheme) { background-image: @toolbarShadowOnTab@, @bgTabTexture@, @@ -77,17 +77,17 @@ } .menu-accel, .menu-iconic-accel { color: graytext; } } -@media all and (-moz-windows-compositor) { +@media (-moz-windows-compositor) { /* These should be hidden w/ glass enabled. Windows draws its own buttons. */ .titlebar-button { display: none; } #main-window[sizemode="maximized"] #titlebar-buttonbox { -moz-margin-end: 3px; } @@ -343,17 +343,17 @@ #minimize-button:-moz-locale-dir(rtl), #restore-button:-moz-locale-dir(rtl), #close-button:-moz-locale-dir(rtl) { -moz-transform: scaleX(-1); } /* ::::: splitmenu highlight style that imitates Windows 7 start menu ::::: */ -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { .splitmenu-menuitem, .splitmenu-menu { -moz-appearance: none; padding-top: 2px; padding-bottom: 2px; border: 1px solid transparent; } .splitmenu-menuitem {
--- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -160,24 +160,24 @@ color: white; text-shadow: 0 0 1px rgba(0,0,0,.7), 0 1px 1.5px rgba(0,0,0,.5); font-weight: bold; padding: 0 1.5em .05em; margin: 0 0 2px; } -@media all and (-moz-windows-classic) { +@media (-moz-windows-classic) { #appmenu-button { margin-bottom: 1px; } } %ifndef WINSTRIPE_AERO -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #main-window[sizemode="normal"] #appmenu-button { margin-bottom: 5px; } } %endif #appmenu-button:hover:active, #appmenu-button[open] { @@ -310,17 +310,17 @@ .appmenu-edit-button[disabled="true"] { opacity: .3; } #appmenuPrimaryPane { -moz-border-end: 1px solid ThreeDShadow; } -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #appmenu-popup { -moz-appearance: none; background: white; border: 1px solid ThreeDShadow; } #appmenuPrimaryPane { background-color: rgba(255,255,255,0.5); padding: 2px; @@ -478,17 +478,17 @@ #main-window[sizemode="normal"] > #titlebar { -moz-appearance: -moz-window-titlebar; } #main-window[sizemode="maximized"] > #titlebar { -moz-appearance: -moz-window-titlebar-maximized; } -@media all and (-moz-windows-classic) { +@media (-moz-windows-classic) { #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container { margin-top: 4px; } } #titlebar-buttonbox { -moz-appearance: -moz-window-button-box; } @@ -1209,17 +1209,17 @@ toolbar[mode="full"] .toolbarbutton-1 > } #urlbar { width: 7em; min-width: 7em; -moz-padding-end: 2px; } -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #urlbar, .searchbar-textbox { @navbarTextboxCustomBorder@ } } #urlbar:-moz-lwtheme, .searchbar-textbox:-moz-lwtheme { @@ -1707,17 +1707,17 @@ richlistitem[type~="action"][actiontype= #editBMPanel_folderTree { min-width: 27em; } .panel-promo-box { margin: 16px 0 -2px; } -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { .panel-promo-box { margin: 8px -16px -16px; padding: 8px 16px; %ifndef WINSTRIPE_AERO border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; %endif background-color: #f1f5fb; @@ -1790,17 +1790,17 @@ richlistitem[type~="action"][actiontype= #TabsToolbar:not(:-moz-lwtheme), #TabsToolbar[tabsontop=false] { background-image: -moz-linear-gradient(bottom, @toolbarShadowColor@ 1px, rgba(0,0,0,.05) 1px, transparent 50%); } %ifndef WINSTRIPE_AERO -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #main-window[sizemode=normal] #TabsToolbar { padding-left: 2px; padding-right: 2px; } } %endif .tabbrowser-tab, @@ -1822,17 +1822,17 @@ richlistitem[type~="action"][actiontype= .tabbrowser-tab:hover, .tabs-newtab-button:hover { background-image: @toolbarShadowOnTab@, @bgTabTextureHover@, -moz-linear-gradient(-moz-dialog, -moz-dialog); } %ifndef WINSTRIPE_AERO -@media all and (-moz-windows-theme: luna-blue) { +@media (-moz-windows-theme: luna-blue) { .tabbrowser-tab, .tabs-newtab-button { background-image: @toolbarShadowOnTab@, -moz-linear-gradient(hsla(51,34%,89%,.9), hsla(51,15%,79%,.9) 1px, hsla(51,9%,68%,.9)); } .tabbrowser-tab:hover, .tabs-newtab-button:hover { @@ -1951,17 +1951,17 @@ richlistitem[type~="action"][actiontype= } .tab-close-button[selected="true"] { -moz-image-region: rect(0, 16px, 16px, 0); } /* Tab scrollbox arrow, tabstrip new tab and all-tabs buttons */ -@media all and (-moz-touch-enabled) { +@media (-moz-touch-enabled) { .tabbrowser-arrowscrollbox > .scrollbutton-up, .tabbrowser-arrowscrollbox > .scrollbutton-down, #TabsToolbar .toolbarbutton-1 { min-width: 8.1mozmm; } .tabs-newtab-button { min-width: 10mozmm;
--- a/browser/themes/winstripe/places/organizer-aero.css +++ b/browser/themes/winstripe/places/organizer-aero.css @@ -16,27 +16,27 @@ #placesToolbar { -moz-appearance: none; background-color: -moz-Dialog; color: -moz-dialogText; } } -@media all and (-moz-windows-compositor) { +@media (-moz-windows-compositor) { #placesToolbox { border-top: none; } #placesToolbar { background-image: -moz-linear-gradient(@toolbarHighlight@, rgba(255,255,255,0)); } } -@media all and (-moz-windows-default-theme) { +@media (-moz-windows-default-theme) { #placesView, #searchModifiers, #infoPane, #placesList, #placeContent { background-color: #EEF3FA; }
--- a/build/mobile/devicemanagerADB.py +++ b/build/mobile/devicemanagerADB.py @@ -107,27 +107,31 @@ class DeviceManagerADB(DeviceManager): # success: remoteDir # failure: None def pushDir(self, localDir, remoteDir): # adb "push" accepts a directory as an argument, but if the directory # contains symbolic links, the links are pushed, rather than the linked # files; we either zip/unzip or push file-by-file to get around this # limitation try: + if (not self.dirExists(remoteDir)): + self.mkDirs(remoteDir+"/x") if (self.useZip): localZip = tempfile.mktemp()+".zip" remoteZip = remoteDir + "/adbdmtmp.zip" subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir) self.pushFile(localZip, remoteZip) os.remove(localZip) - self.checkCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]) + data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read() self.checkCmdAs(["shell", "rm", remoteZip]) + if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)): + print "zip/unzip failure: falling back to normal push" + self.useZip = False + self.pushDir(localDir, remoteDir) else: - if (not self.dirExists(remoteDir)): - self.mkDirs(remoteDir+"/x") for root, dirs, files in os.walk(localDir, followlinks='true'): relRoot = os.path.relpath(root, localDir) for file in files: localFile = os.path.join(root, file) remoteFile = remoteDir + "/" if (relRoot!="."): remoteFile = remoteFile + relRoot + "/" remoteFile = remoteFile + file @@ -604,17 +608,17 @@ class DeviceManagerADB(DeviceManager): if (self.fileExists(devroot + "/sanity/tmpfile")): print "will execute commands via run-as " + packageName self.packageName = packageName self.useRunAs = True self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"]) self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"]) def isUnzipAvailable(self): - data = self.runCmd(["shell", "unzip"]).stdout.read() + data = self.runCmdAs(["shell", "unzip"]).stdout.read() if (re.search('Usage', data)): return True else: return False def isLocalZipAvailable(self): try: subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
--- a/build/mobile/robocop/Actions.java.in +++ b/build/mobile/robocop/Actions.java.in @@ -39,22 +39,31 @@ package @ANDROID_PACKAGE_NAME@; import java.util.List; public interface Actions { public enum SpecialKey { DOWN, UP, LEFT, RIGHT, ENTER } + + public interface EventExpecter { + /** Blocks until the event has been received. Subsequent calls will return immediately. */ + public void blockForEvent(); + /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */ + public boolean eventReceived(); + } + /** - * Waits for a gecko event to be sent from the Gecko instance. + * Listens for a gecko event to be sent from the Gecko instance. + * The returned object can be used to test if the event has been + * received. Note that only one event is listened for. * * @param geckoEvent The geckoEvent JSONObject's type */ - - void waitForGeckoEvent(String geckoEvent); + EventExpecter expectGeckoEvent(String geckoEvent); // Send the string kewsToSend to the application void sendKeys(String keysToSend); //Send any of the above keys to the element void sendSpecialKey(SpecialKey button); void drag(int startingX, int endingX, int startingY, int endingY); }
--- a/build/mobile/robocop/Driver.java.in +++ b/build/mobile/robocop/Driver.java.in @@ -33,27 +33,30 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package @ANDROID_PACKAGE_NAME@; + import java.util.List; +import android.app.Activity; public interface Driver { /** * Find the first Element using the given method. * + * @param activity The activity the element belongs to * @param name The name of the element * @return The first matching element on the current context * @throws RoboCopException If no matching elements are found */ - Element findElement(String name); + Element findElement(Activity activity, String name); /** * Sets up scroll handling so that data is received from the extension. */ void setupScrollHandling(); int getPageHeight(); int getScrollHeight();
--- a/build/mobile/robocop/FennecNativeActions.java.in +++ b/build/mobile/robocop/FennecNativeActions.java.in @@ -79,19 +79,16 @@ public class FennecNativeActions impleme private Class gel; private Class ge; private Class gas; private Method registerGEL; private Method unregisterGEL; private Method sendGE; - // If waiting for an event. - private SynchronousQueue waitqueue = new SynchronousQueue<Boolean>(); - public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){ this.solo = robocop; this.instr = instrumentation; // Set up reflexive access of java classes and methods. try { classLoader = activity.getClassLoader(); gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener"); ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent"); @@ -111,61 +108,106 @@ public class FennecNativeActions impleme } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } class wakeInvocationHandler implements InvocationHandler { - public wakeInvocationHandler(){}; + private final GeckoEventExpecter mEventExpecter; + + public wakeInvocationHandler(GeckoEventExpecter expecter) { + mEventExpecter = expecter; + } + public Object invoke(Object proxy, Method method, Object[] args) { String methodName = method.getName(); //Depending on the method, return a completely different type. if(methodName.equals("toString")) { return "wakeInvocationHandler"; } if(methodName.equals("equals")) { return this == args[0]; } if(methodName.equals("clone")) { return this; } if(methodName.equals("hashCode")) { return 314; } Log.i("Robocop", "Waking up on "+methodName); - waitqueue.offer(new Boolean(true)); + mEventExpecter.notifyOfEvent(); return null; } } + + class GeckoEventExpecter implements EventExpecter { + private final String mGeckoEvent; + private final Object[] mRegistrationParams; + private boolean mEventReceived; + + GeckoEventExpecter(String geckoEvent, Object[] registrationParams) { + mGeckoEvent = geckoEvent; + mRegistrationParams = registrationParams; + } + + public synchronized void blockForEvent() { + while (! mEventReceived) { + try { + this.wait(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + break; + } + } + Log.i("Robocop", "unblocked on expecter for " + mGeckoEvent); + } + + public synchronized boolean eventReceived() { + return mEventReceived; + } + + void notifyOfEvent() { + try { + unregisterGEL.invoke(null, mRegistrationParams); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + Log.i("Robocop", "received event " + mGeckoEvent); + synchronized (this) { + mEventReceived = true; + this.notifyAll(); + } + } + } - public void waitForGeckoEvent(String geckoEvent) { + public EventExpecter expectGeckoEvent(String geckoEvent) { Log.i("Robocop", "waiting for "+geckoEvent); try { Class [] interfaces = new Class[1]; interfaces[0] = gel; Object[] finalParams = new Object[2]; finalParams[0] = geckoEvent; - wakeInvocationHandler wIH = new wakeInvocationHandler(); + GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams); + wakeInvocationHandler wIH = new wakeInvocationHandler(expecter); Object proxy = Proxy.newProxyInstance(classLoader, interfaces, wIH); finalParams[1] = proxy; registerGEL.invoke(null, finalParams); - waitqueue.take(); - unregisterGEL.invoke(null, finalParams); + return expecter; } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); } - Log.i("Robocop", "wait ends for: "+geckoEvent); + return null; } public void sendSpecialKey(SpecialKey button) { switch( button) { case DOWN: instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN); break; case UP:
--- a/build/mobile/robocop/FennecNativeDriver.java.in +++ b/build/mobile/robocop/FennecNativeDriver.java.in @@ -162,18 +162,17 @@ public class FennecNativeDriver implemen } public int getGeckoWidth() { if(!geckoInfo) { getGeckoInfo(); } return geckoWidth; } - @Override - public Element findElement(String name) { + public Element findElement(Activity activity, String name) { if (name == null) throw new IllegalArgumentException("Can not findElements when passed a null"); if (locators.containsKey(name)){ return new FennecNativeElement(Integer.decode((String)locators.get(name)), activity, solo); } throw new RoboCopException("Element does not exist in the list"); }
--- a/build/mobile/robocop/FennecNativeElement.java.in +++ b/build/mobile/robocop/FennecNativeElement.java.in @@ -51,64 +51,59 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.TextSwitcher; import android.app.Instrumentation; import android.util.Log; import com.jayway.android.robotium.solo.Solo; import java.util.concurrent.SynchronousQueue; public class FennecNativeElement implements Element { + private final Activity mActivity; private Integer id; - private Activity currentActivity; private Solo robocop; public FennecNativeElement(Integer id, Activity activity, Solo solo){ this.id = id; + mActivity = activity; robocop = solo; - currentActivity = activity; } public Integer getId() { return id; } - @Override public void click() { final SynchronousQueue syncQueue = new SynchronousQueue(); - currentActivity = robocop.getCurrentActivity(); - currentActivity.runOnUiThread( + mActivity.runOnUiThread( new Runnable() { public void run() { - View view = (View)currentActivity.findViewById(id); + View view = (View)mActivity.findViewById(id); if(view != null) { view.performClick(); } else { throw new RoboCopException("click: unable to find view "+id); } syncQueue.offer(new Object()); } }); try { syncQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } private Object text; - private Activity elementActivity; - @Override public String getText() { - elementActivity = robocop.getCurrentActivity(); final SynchronousQueue syncQueue = new SynchronousQueue(); - elementActivity.runOnUiThread( + mActivity.runOnUiThread( new Runnable() { public void run() { - View v = elementActivity.findViewById(id); + View v = mActivity.findViewById(id); if(v instanceof EditText) { EditText et = (EditText)v; text = et.getEditableText(); }else if(v instanceof TextSwitcher) { TextSwitcher ts = (TextSwitcher)v; ts.getNextView(); text = ((TextView)ts.getCurrentView()).getText(); }else if(v instanceof ViewGroup) { @@ -136,14 +131,32 @@ public class FennecNativeElement impleme e.printStackTrace(); } if(text == null) { throw new RoboCopException("getText: Text is null for view "+id); } return text.toString(); } - @Override + private boolean displayed; + public boolean isDisplayed() { - // TODO Auto-generated method stub - return false; + final SynchronousQueue syncQueue = new SynchronousQueue(); + displayed = false; + mActivity.runOnUiThread( + new Runnable() { + public void run() { + View view = (View)mActivity.findViewById(id); + if(view != null) { + displayed = true; + } + syncQueue.offer(new Object()); + } + }); + try { + syncQueue.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return displayed; } + }
--- a/content/base/public/nsISelectionController.idl +++ b/content/base/public/nsISelectionController.idl @@ -46,17 +46,17 @@ typedef short SelectionType; typedef short SelectionRegion; %} interface nsIDOMNode; interface nsISelection; interface nsISelectionDisplay; -[scriptable, uuid(e0dd9365-470b-4ee8-b644-54add1c4c73f)] +[scriptable, uuid(cf30315f-b65d-44c3-8c57-557e36d18fd2)] interface nsISelectionController : nsISelectionDisplay { const short SELECTION_NONE=0; const short SELECTION_NORMAL=1; const short SELECTION_SPELLCHECK=2; const short SELECTION_IME_RAWINPUT=4; const short SELECTION_IME_SELECTEDRAWTEXT=8; const short SELECTION_IME_CONVERTEDTEXT=16; @@ -253,20 +253,21 @@ interface nsISelectionController : nsISe */ void scrollPage(in boolean forward); /** ScrolLine will scroll line up or down dependent on the boolean * @param aForward scroll forward or backwards in selection */ void scrollLine(in boolean forward); - /** ScrolHorizontal will scroll left or right dependent on the boolean - * @param aLeft if true will scroll left. if not will scroll right. + /** ScrollCharacter will scroll right or left dependent on the boolean + * @param aRight if true will scroll right. if not will scroll left. */ - void scrollHorizontal(in boolean left); + void scrollCharacter(in boolean right); + /** SelectAll will select the whole page */ void selectAll(); /** CheckVisibility will return true if textnode and offsets are actually rendered * in the current precontext. * @param aNode textNode to test * @param aStartOffset offset in dom to first char of textnode to test
--- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -8902,35 +8902,37 @@ nsDocument::RequestFullScreen(Element* a // document, as required by the spec. for (PRUint32 i = 0; i < changed.Length(); ++i) { DispatchFullScreenChange(changed[changed.Length() - i - 1]); } // Remember this is the requesting full-screen document. sFullScreenDoc = do_GetWeakReference(static_cast<nsIDocument*>(this)); - // Make the window full-screen. Note we must make the state changes above - // before making the window full-screen, as then the document reports as - // being in full-screen mode when the chrome "fullscreen" event fires, - // enabling chrome to distinguish between browser and dom full-screen - // modes. Also note that nsGlobalWindow::SetFullScreen() (which - // SetWindowFullScreen() calls) proxies to the root window in its hierarchy, - // and does not operate on the a per-nsIDOMWindow basis. - SetWindowFullScreen(this, true); - #ifdef DEBUG + // Note assertions must run before SetWindowFullScreen() as that does + // synchronous event dispatch which can run script which exits full-screen! NS_ASSERTION(GetFullScreenElement() == aElement, "Full-screen element should be the requested element!"); NS_ASSERTION(IsFullScreenDoc(), "Should be full-screen doc"); nsCOMPtr<nsIDOMHTMLElement> fse; GetMozFullScreenElement(getter_AddRefs(fse)); nsCOMPtr<nsIContent> c(do_QueryInterface(fse)); NS_ASSERTION(c->AsElement() == aElement, "GetMozFullScreenElement should match GetFullScreenElement()"); #endif + + // Make the window full-screen. Note we must make the state changes above + // before making the window full-screen, as then the document reports as + // being in full-screen mode when the chrome "fullscreen" event fires, + // enabling chrome to distinguish between browser and dom full-screen + // modes. Also note that nsGlobalWindow::SetFullScreen() (which + // SetWindowFullScreen() calls) proxies to the root window in its hierarchy, + // and does not operate on the a per-nsIDOMWindow basis. + SetWindowFullScreen(this, true); } NS_IMETHODIMP nsDocument::GetMozFullScreenElement(nsIDOMHTMLElement **aFullScreenElement) { NS_ENSURE_ARG_POINTER(aFullScreenElement); *aFullScreenElement = nsnull; if (IsFullScreenDoc()) {
--- a/content/base/src/nsImageLoadingContent.cpp +++ b/content/base/src/nsImageLoadingContent.cpp @@ -154,20 +154,21 @@ nsImageLoadingContent::~nsImageLoadingCo } \ PR_END_MACRO /* * imgIContainerObserver impl */ NS_IMETHODIMP -nsImageLoadingContent::FrameChanged(imgIContainer* aContainer, +nsImageLoadingContent::FrameChanged(imgIRequest* aRequest, + imgIContainer* aContainer, const nsIntRect* aDirtyRect) { - LOOP_OVER_OBSERVERS(FrameChanged(aContainer, aDirtyRect)); + LOOP_OVER_OBSERVERS(FrameChanged(aRequest, aContainer, aDirtyRect)); return NS_OK; } /* * imgIDecoderObserver impl */ NS_IMETHODIMP nsImageLoadingContent::OnStartRequest(imgIRequest* aRequest)
--- a/content/base/src/nsStubImageDecoderObserver.cpp +++ b/content/base/src/nsStubImageDecoderObserver.cpp @@ -108,13 +108,14 @@ nsStubImageDecoderObserver::OnDiscard(im NS_IMETHODIMP nsStubImageDecoderObserver::OnImageIsAnimated(imgIRequest *aRequest) { return NS_OK; } NS_IMETHODIMP -nsStubImageDecoderObserver::FrameChanged(imgIContainer *aContainer, +nsStubImageDecoderObserver::FrameChanged(imgIRequest* aRequest, + imgIContainer *aContainer, const nsIntRect *aDirtyRect) { return NS_OK; }
--- a/content/base/src/nsWebSocket.cpp +++ b/content/base/src/nsWebSocket.cpp @@ -1563,16 +1563,17 @@ NS_IMETHODIMP nsWebSocket::Cancel(nsresult aStatus) { NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (mDisconnected) return NS_OK; ConsoleError(); + mClientReasonCode = nsIWebSocketChannel::CLOSE_GOING_AWAY; return CloseConnection(); } NS_IMETHODIMP nsWebSocket::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp @@ -92,17 +92,16 @@ #include "imgIEncoder.h" #include "gfxContext.h" #include "gfxASurface.h" #include "gfxImageSurface.h" #include "gfxPlatform.h" #include "gfxFont.h" -#include "gfxTextRunCache.h" #include "gfxBlur.h" #include "gfxUtils.h" #include "nsFrameManager.h" #include "nsFrameLoader.h" #include "nsBidi.h" #include "nsBidiPresUtils.h" #include "Layers.h" @@ -2732,22 +2731,21 @@ nsCanvasRenderingContext2D::MeasureText( /** * Used for nsBidiPresUtils::ProcessText */ struct NS_STACK_CLASS nsCanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor { virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction) { - mTextRun = gfxTextRunCache::MakeTextRun(text, - length, - mFontgrp, - mThebes, - mAppUnitsPerDevPixel, - direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); + mTextRun = mFontgrp->MakeTextRun(text, + length, + mThebes, + mAppUnitsPerDevPixel, + direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); } virtual nscoord GetWidth() { gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, mTextRun->GetLength(), mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS : @@ -2806,17 +2804,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces point, 0, mTextRun->GetLength(), nsnull, nsnull); } // current text run - gfxTextRunCache::AutoTextRun mTextRun; + nsAutoPtr<gfxTextRun> mTextRun; // pointer to the context, may not be the canvas's context // if an intermediate surface is being used gfxContext* mThebes; // position of the left side of the string, alphabetic baseline gfxPoint mPt; @@ -3150,18 +3148,18 @@ gfxTextRun* nsCanvasRenderingContext2D::MakeTextRun(const PRUnichar* aText, PRUint32 aLength, PRUint32 aAppUnitsPerDevUnit, PRUint32 aFlags) { gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); if (!currentFontStyle) return nsnull; - return gfxTextRunCache::MakeTextRun(aText, aLength, currentFontStyle, - mThebes, aAppUnitsPerDevUnit, aFlags); + return currentFontStyle->MakeTextRun(aText, aLength, + mThebes, aAppUnitsPerDevUnit, aFlags); } // // line caps/joins // NS_IMETHODIMP nsCanvasRenderingContext2D::SetLineWidth(float width)
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp @@ -89,17 +89,16 @@ #include "imgIEncoder.h" #include "gfxContext.h" #include "gfxASurface.h" #include "gfxImageSurface.h" #include "gfxPlatform.h" #include "gfxFont.h" -#include "gfxTextRunCache.h" #include "gfxBlur.h" #include "gfxUtils.h" #include "nsFrameManager.h" #include "nsFrameLoader.h" #include "nsBidi.h" #include "nsBidiPresUtils.h" #include "Layers.h" @@ -2937,22 +2936,21 @@ nsCanvasRenderingContext2DAzure::Measure * Used for nsBidiPresUtils::ProcessText */ struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiProcessor { typedef nsCanvasRenderingContext2DAzure::ContextState ContextState; virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction) { - mTextRun = gfxTextRunCache::MakeTextRun(text, - length, - mFontgrp, - mThebes, - mAppUnitsPerDevPixel, - direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); + mTextRun = mFontgrp->MakeTextRun(text, + length, + mThebes, + mAppUnitsPerDevPixel, + direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); } virtual nscoord GetWidth() { gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, mTextRun->GetLength(), mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS : @@ -3091,17 +3089,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces state.dashOffset), DrawOptions(state.globalAlpha, mCtx->UsedOperation())); } } } // current text run - gfxTextRunCache::AutoTextRun mTextRun; + nsAutoPtr<gfxTextRun> mTextRun; // pointer to a screen reference context used to measure text and such nsRefPtr<gfxContext> mThebes; // Pointer to the draw target we should fill our text to nsCanvasRenderingContext2DAzure *mCtx; // position of the left side of the string, alphabetic baseline
--- a/content/html/content/src/nsHTMLMediaElement.cpp +++ b/content/html/content/src/nsHTMLMediaElement.cpp @@ -297,16 +297,21 @@ void nsHTMLMediaElement::ReportLoadError aParamCount); } NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { nsContentUtils::UnregisterShutdownObserver(this); + if (!mElement) { + // We've been notified by the shutdown observer, and are shutting down. + return NS_BINDING_ABORTED; + } + // The element is only needed until we've had a chance to call // InitializeDecoderForChannel. So make sure mElement is cleared here. nsRefPtr<nsHTMLMediaElement> element; element.swap(mElement); if (mLoadID != element->GetCurrentLoadID()) { // The channel has been cancelled before we had a chance to create // a decoder. Abort, don't dispatch an "error" event, as the new load
--- a/content/html/content/src/nsTextEditorState.cpp +++ b/content/html/content/src/nsTextEditorState.cpp @@ -209,17 +209,17 @@ public: NS_IMETHOD WordExtendForDelete(bool aForward); NS_IMETHOD LineMove(bool aForward, bool aExtend); NS_IMETHOD IntraLineMove(bool aForward, bool aExtend); NS_IMETHOD PageMove(bool aForward, bool aExtend); NS_IMETHOD CompleteScroll(bool aForward); NS_IMETHOD CompleteMove(bool aForward, bool aExtend); NS_IMETHOD ScrollPage(bool aForward); NS_IMETHOD ScrollLine(bool aForward); - NS_IMETHOD ScrollHorizontal(bool aLeft); + NS_IMETHOD ScrollCharacter(bool aRight); NS_IMETHOD SelectAll(void); NS_IMETHOD CheckVisibility(nsIDOMNode *node, PRInt16 startOffset, PRInt16 EndOffset, bool *_retval); private: nsRefPtr<nsFrameSelection> mFrameSelection; nsCOMPtr<nsIContent> mLimiter; nsIScrollableFrame *mScrollFrame; nsWeakPtr mPresShellWeak; @@ -568,22 +568,22 @@ nsTextInputSelectionImpl::ScrollLine(boo mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), nsIScrollableFrame::LINES, nsIScrollableFrame::SMOOTH); return NS_OK; } NS_IMETHODIMP -nsTextInputSelectionImpl::ScrollHorizontal(bool aLeft) +nsTextInputSelectionImpl::ScrollCharacter(bool aRight) { if (!mScrollFrame) return NS_ERROR_NOT_INITIALIZED; - mScrollFrame->ScrollBy(nsIntPoint(aLeft ? -1 : 1, 0), + mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), nsIScrollableFrame::LINES, nsIScrollableFrame::SMOOTH); return NS_OK; } NS_IMETHODIMP nsTextInputSelectionImpl::SelectAll() {
--- a/content/media/nsBuiltinDecoder.cpp +++ b/content/media/nsBuiltinDecoder.cpp @@ -268,17 +268,17 @@ nsresult nsBuiltinDecoder::Play() ChangeState(PLAY_STATE_PLAYING); return NS_OK; } /** * Returns true if aValue is inside a range of aRanges, and put the range * index in aIntervalIndex if it is not null. * If aValue is not inside a range, false is returned, and aIntervalIndex, if - * not null, is set to the index of the range which ends immediatly before aValue + * not null, is set to the index of the range which ends immediately before aValue * (and can be -1 if aValue is before aRanges.Start(0)). */ static bool IsInRanges(nsTimeRanges& aRanges, double aValue, PRInt32& aIntervalIndex) { PRUint32 length; aRanges.GetLength(&length); for (PRUint32 i = 0; i < length; i++) { double start, end; aRanges.Start(i, &start); @@ -310,40 +310,42 @@ nsresult nsBuiltinDecoder::Seek(double a NS_ENSURE_SUCCESS(res, NS_OK); seekable.GetLength(&length); if (!length) { return NS_OK; } // If the position we want to seek to is not in a seekable range, we seek - // to the closest position in the seekable ranges instead . If two positions - // are equaly close, we seek to the closest position from the currentTime. + // to the closest position in the seekable ranges instead. If two positions + // are equally close, we seek to the closest position from the currentTime. // See seeking spec, point 7 : - // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#seeking + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking PRInt32 range = 0; if (!IsInRanges(seekable, aTime, range)) { if (range != -1) { - double leftBound, rightBound; - res = seekable.End(range, &leftBound); - NS_ENSURE_SUCCESS(res, NS_OK); - double distanceLeft = NS_ABS(leftBound - aTime); - - double distanceRight = -1; if (range + 1 < length) { - res = seekable.Start(range+1, &rightBound); + double leftBound, rightBound; + res = seekable.End(range, &leftBound); + NS_ENSURE_SUCCESS(res, NS_OK); + res = seekable.Start(range + 1, &rightBound); NS_ENSURE_SUCCESS(res, NS_OK); - distanceRight = NS_ABS(rightBound - aTime); + double distanceLeft = NS_ABS(leftBound - aTime); + double distanceRight = NS_ABS(rightBound - aTime); + if (distanceLeft == distanceRight) { + distanceLeft = NS_ABS(leftBound - mCurrentTime); + distanceRight = NS_ABS(rightBound - mCurrentTime); + } + aTime = (distanceLeft < distanceRight) ? leftBound : rightBound; + } else { + // Seek target is after the end last range in seekable data. + // Clamp the seek target to the end of the last seekable range. + res = seekable.End(length - 1, &aTime); + NS_ENSURE_SUCCESS(res, NS_OK); } - - if (distanceLeft == distanceRight) { - distanceLeft = NS_ABS(leftBound - mCurrentTime); - distanceRight = NS_ABS(rightBound - mCurrentTime); - } - aTime = (distanceLeft < distanceRight) ? leftBound : rightBound; } else { // aTime is before the first range in |seekable|, the closest point we can // seek to is the start of the first range. seekable.Start(0, &aTime); } } mRequestedSeekTime = aTime;
--- a/content/media/nsBuiltinDecoderStateMachine.cpp +++ b/content/media/nsBuiltinDecoderStateMachine.cpp @@ -1119,16 +1119,43 @@ void nsBuiltinDecoderStateMachine::Reset { NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); mVideoFrameEndTime = -1; mAudioStartTime = -1; mAudioEndTime = -1; mAudioCompleted = false; } +void nsBuiltinDecoderStateMachine::NotifyDataArrived(const char* aBuffer, + PRUint32 aLength, + PRUint32 aOffset) +{ + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + mReader->NotifyDataArrived(aBuffer, aLength, aOffset); + + // While playing an unseekable stream of unknown duration, mEndTime is + // updated (in AdvanceFrame()) as we play. But if data is being downloaded + // faster than played, mEndTime won't reflect the end of playable data + // since we haven't played the frame at the end of buffered data. So update + // mEndTime here as new data is downloaded to prevent such a lag. + nsTimeRanges buffered; + if (mDecoder->IsInfinite() && + NS_SUCCEEDED(mDecoder->GetBuffered(&buffered))) + { + PRUint32 length = 0; + buffered.GetLength(&length); + if (length) { + double end = 0; + buffered.End(length - 1, &end); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mEndTime = NS_MAX<PRInt64>(mEndTime, end * USECS_PER_S); + } + } +} + void nsBuiltinDecoderStateMachine::Seek(double aTime) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // nsBuiltinDecoder::mPlayState should be SEEKING while we seek, and // in that case nsBuiltinDecoder shouldn't be calling us. NS_ASSERTION(mState != DECODER_STATE_SEEKING, "We shouldn't already be seeking"); @@ -1500,17 +1527,21 @@ void nsBuiltinDecoderStateMachine::Decod LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n", mDecoder.get(), mCurrentFrameTime)); // Change state to DECODING or COMPLETED now. SeekingStopped will // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING // if we need to seek again. nsCOMPtr<nsIRunnable> stopEvent; - if (GetMediaTime() == mEndTime) { + bool isLiveStream = mDecoder->GetStream()->GetLength() == -1; + if (GetMediaTime() == mEndTime && !isLiveStream) { + // Seeked to end of media, move to COMPLETED state. Note we don't do + // this if we're playing a live stream, since the end of media will advance + // once we download more data! LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED", mDecoder.get(), seekTime)); stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd); mState = DECODER_STATE_COMPLETED; } else { LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING", mDecoder.get(), seekTime)); stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
--- a/content/media/nsBuiltinDecoderStateMachine.h +++ b/content/media/nsBuiltinDecoderStateMachine.h @@ -218,20 +218,17 @@ public: PRInt64 AudioQueueMemoryInUse() { if (mReader) { return mReader->AudioQueueMemoryInUse(); } return 0; } - void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) { - NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); - mReader->NotifyDataArrived(aBuffer, aLength, aOffset); - } + void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset); PRInt64 GetEndMediaTime() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); return mEndTime; } bool IsSeekable() { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
--- a/content/svg/content/src/nsSVGFilters.cpp +++ b/content/svg/content/src/nsSVGFilters.cpp @@ -5737,21 +5737,22 @@ nsSVGFEImageElement::OnStopDecode(imgIRe { nsresult rv = nsImageLoadingContent::OnStopDecode(aRequest, status, statusArg); Invalidate(); return rv; } NS_IMETHODIMP -nsSVGFEImageElement::FrameChanged(imgIContainer *aContainer, +nsSVGFEImageElement::FrameChanged(imgIRequest* aRequest, + imgIContainer *aContainer, const nsIntRect *aDirtyRect) { nsresult rv = - nsImageLoadingContent::FrameChanged(aContainer, aDirtyRect); + nsImageLoadingContent::FrameChanged(aRequest, aContainer, aDirtyRect); Invalidate(); return rv; } NS_IMETHODIMP nsSVGFEImageElement::OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer) {
--- a/content/svg/content/src/nsSVGFilters.h +++ b/content/svg/content/src/nsSVGFilters.h @@ -293,17 +293,18 @@ public: nsIContent* aBindingParent, bool aCompileEventHandlers); virtual nsEventStates IntrinsicState() const; // imgIDecoderObserver NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg); // imgIContainerObserver - NS_IMETHOD FrameChanged(imgIContainer *aContainer, + NS_IMETHOD FrameChanged(imgIRequest* aRequest, + imgIContainer *aContainer, const nsIntRect *aDirtyRect); // imgIContainerObserver NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer); void MaybeLoadSVGImage(); virtual nsXPCClassInfo* GetClassInfo();
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -1614,16 +1614,65 @@ nsDOMWindowUtils::GetLayerManagerType(ns if (!mgr) return NS_ERROR_FAILURE; mgr->GetBackendName(aType); return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::StartFrameTimeRecording() +{ + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return NS_ERROR_FAILURE; + + LayerManager *mgr = widget->GetLayerManager(); + if (!mgr) + return NS_ERROR_FAILURE; + + mgr->StartFrameTimeRecording(); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWindowUtils::StopFrameTimeRecording(PRUint32 *frameCount NS_OUTPARAM, float **frames NS_OUTPARAM) +{ + NS_ENSURE_ARG_POINTER(frameCount); + NS_ENSURE_ARG_POINTER(frames); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) + return NS_ERROR_FAILURE; + + LayerManager *mgr = widget->GetLayerManager(); + if (!mgr) + return NS_ERROR_FAILURE; + + nsTArray<float> frameTimes = mgr->StopFrameTimeRecording(); + + *frames = nsnull; + *frameCount = frameTimes.Length(); + + if (*frameCount != 0) { + *frames = (float*)nsMemory::Alloc(*frameCount * sizeof(float*)); + if (!*frames) + return NS_ERROR_OUT_OF_MEMORY; + + /* copy over the frame times into the array we just allocated */ + for (PRUint32 i = 0; i < *frameCount; i++) { + (*frames)[i] = frameTimes[i]; + } + } + + return NS_OK; +} + static bool ComputeAnimationValue(nsCSSProperty aProperty, Element* aElement, const nsAString& aInput, nsStyleAnimation::Value& aOutput) { if (!nsStyleAnimation::ComputeValue(aProperty, aElement, aInput,
--- a/dom/base/nsGlobalWindowCommands.cpp +++ b/dom/base/nsGlobalWindowCommands.cpp @@ -40,16 +40,17 @@ #include "nsGlobalWindowCommands.h" #include "nsIComponentManager.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsCRT.h" #include "nsString.h" #include "mozilla/Preferences.h" +#include "mozilla/Util.h" #include "nsIControllerCommandTable.h" #include "nsICommandParams.h" #include "nsPIDOMWindow.h" #include "nsIPresShell.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" @@ -71,16 +72,20 @@ const char * const sSelectNoneString = " const char * const sCopyImageLocationString = "cmd_copyImageLocation"; const char * const sCopyImageContentsString = "cmd_copyImageContents"; const char * const sCopyImageString = "cmd_copyImage"; const char * const sScrollTopString = "cmd_scrollTop"; const char * const sScrollBottomString = "cmd_scrollBottom"; const char * const sScrollPageUpString = "cmd_scrollPageUp"; const char * const sScrollPageDownString = "cmd_scrollPageDown"; +const char * const sScrollLineUpString = "cmd_scrollLineUp"; +const char * const sScrollLineDownString = "cmd_scrollLineDown"; +const char * const sScrollLeftString = "cmd_scrollLeft"; +const char * const sScrollRightString = "cmd_scrollRight"; const char * const sMoveTopString = "cmd_moveTop"; const char * const sMoveBottomString = "cmd_moveBottom"; const char * const sMovePageUpString = "cmd_movePageUp"; const char * const sMovePageDownString = "cmd_movePageDown"; const char * const sLinePreviousString = "cmd_linePrevious"; const char * const sLineNextString = "cmd_lineNext"; const char * const sCharPreviousString = "cmd_charPrevious"; const char * const sCharNextString = "cmd_charNext"; @@ -116,50 +121,44 @@ const char * const sSelectBottomString = #endif // a base class for selection-related commands, for code sharing class nsSelectionCommandsBase : public nsIControllerCommand { public: NS_DECL_ISUPPORTS - NS_DECL_NSICONTROLLERCOMMAND + NS_IMETHOD IsCommandEnabled(const char * aCommandName, nsISupports *aCommandContext, bool *_retval NS_OUTPARAM); + NS_IMETHOD GetCommandStateParams(const char * aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext); + NS_IMETHOD DoCommandParams(const char * aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext); protected: - // subclasses override DoSelectCommand - virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow) = 0; - - static nsresult GetPresShellFromWindow(nsIDOMWindow *aWindow, nsIPresShell **aPresShell); - static nsresult GetSelectionControllerFromWindow(nsIDOMWindow *aWindow, nsISelectionController **aSelCon); + static nsresult GetPresShellFromWindow(nsPIDOMWindow *aWindow, nsIPresShell **aPresShell); + static nsresult GetSelectionControllerFromWindow(nsPIDOMWindow *aWindow, nsISelectionController **aSelCon); // no member variables, please, we're stateless! }; // this class implements commands whose behavior depends on the 'browse with caret' setting class nsSelectMoveScrollCommand : public nsSelectionCommandsBase { -protected: +public: - virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow); - - nsresult DoCommandBrowseWithCaretOn(const char *aCommandName, - nsIDOMWindow *aWindow, - nsISelectionController *aSelectionController); - nsresult DoCommandBrowseWithCaretOff(const char *aCommandName, nsISelectionController *aSelectionController); + NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandContext); // no member variables, please, we're stateless! }; // this class implements other selection commands class nsSelectCommand : public nsSelectionCommandsBase { -protected: +public: - virtual nsresult DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow); + NS_IMETHOD DoCommand(const char * aCommandName, nsISupports *aCommandContext); // no member variables, please, we're stateless! }; #if 0 #pragma mark - #endif @@ -182,223 +181,147 @@ nsSelectionCommandsBase::IsCommandEnable NS_IMETHODIMP nsSelectionCommandsBase::GetCommandStateParams(const char *aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext) { // XXX we should probably return the enabled state return NS_ERROR_NOT_IMPLEMENTED; } -NS_IMETHODIMP -nsSelectionCommandsBase::DoCommand(const char *aCommandName, nsISupports *aCommandContext) -{ - nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(aCommandContext); - NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); - - return DoSelectCommand(aCommandName, window); -} - /* void doCommandParams (in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); */ NS_IMETHODIMP nsSelectionCommandsBase::DoCommandParams(const char *aCommandName, nsICommandParams *aParams, nsISupports *aCommandContext) { return DoCommand(aCommandName, aCommandContext); } // protected methods nsresult -nsSelectionCommandsBase::GetPresShellFromWindow(nsIDOMWindow *aWindow, nsIPresShell **aPresShell) +nsSelectionCommandsBase::GetPresShellFromWindow(nsPIDOMWindow *aWindow, nsIPresShell **aPresShell) { *aPresShell = nsnull; + NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE); - nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aWindow)); - NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); - - nsIDocShell *docShell = win->GetDocShell(); + nsIDocShell *docShell = aWindow->GetDocShell(); NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); return docShell->GetPresShell(aPresShell); } nsresult -nsSelectionCommandsBase::GetSelectionControllerFromWindow(nsIDOMWindow *aWindow, nsISelectionController **aSelCon) +nsSelectionCommandsBase::GetSelectionControllerFromWindow(nsPIDOMWindow *aWindow, nsISelectionController **aSelCon) { *aSelCon = nsnull; nsCOMPtr<nsIPresShell> presShell; GetPresShellFromWindow(aWindow, getter_AddRefs(presShell)); if (presShell) return CallQueryInterface(presShell, aSelCon); return NS_ERROR_FAILURE; } #if 0 #pragma mark - #endif +static const struct BrowseCommand { + const char *reverse, *forward; + nsresult (NS_STDCALL nsISelectionController::*scroll)(bool); + nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool); +} browseCommands[] = { + { sScrollTopString, sScrollBottomString, + &nsISelectionController::CompleteScroll }, + { sScrollPageUpString, sScrollPageDownString, + &nsISelectionController::ScrollPage }, + { sScrollLineUpString, sScrollLineDownString, + &nsISelectionController::ScrollLine }, + { sScrollLeftString, sScrollRightString, + &nsISelectionController::ScrollCharacter }, + { sMoveTopString, sMoveBottomString, + &nsISelectionController::CompleteScroll, + &nsISelectionController::CompleteMove }, + { sMovePageUpString, sMovePageDownString, + &nsISelectionController::ScrollPage, + &nsISelectionController::PageMove }, + { sLinePreviousString, sLineNextString, + &nsISelectionController::ScrollLine, + &nsISelectionController::LineMove }, + { sWordPreviousString, sWordNextString, + &nsISelectionController::ScrollCharacter, + &nsISelectionController::WordMove }, + { sCharPreviousString, sCharNextString, + &nsISelectionController::ScrollCharacter, + &nsISelectionController::CharacterMove }, + { sBeginLineString, sEndLineString, + &nsISelectionController::CompleteScroll, + &nsISelectionController::IntraLineMove } +}; + nsresult -nsSelectMoveScrollCommand::DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow) +nsSelectMoveScrollCommand::DoCommand(const char *aCommandName, nsISupports *aCommandContext) { + nsCOMPtr<nsPIDOMWindow> piWindow(do_QueryInterface(aCommandContext)); nsCOMPtr<nsISelectionController> selCont; - GetSelectionControllerFromWindow(aWindow, getter_AddRefs(selCont)); + GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont)); NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED); // We allow the caret to be moved with arrow keys on any window for which // the caret is enabled. In particular, this includes caret-browsing mode // in non-chrome documents. bool caretOn = false; selCont->GetCaretEnabled(&caretOn); if (!caretOn) { caretOn = Preferences::GetBool("accessibility.browsewithcaret"); if (caretOn) { - nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow); - if (piWindow) { - nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(piWindow->GetDocShell()); - if (dsti) { - PRInt32 itemType; - dsti->GetItemType(&itemType); - if (itemType == nsIDocShellTreeItem::typeChrome) { - caretOn = false; - } + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(piWindow->GetDocShell()); + if (dsti) { + PRInt32 itemType; + dsti->GetItemType(&itemType); + if (itemType == nsIDocShellTreeItem::typeChrome) { + caretOn = false; } } } } - if (caretOn) { - return DoCommandBrowseWithCaretOn(aCommandName, aWindow, selCont); - } - - return DoCommandBrowseWithCaretOff(aCommandName, selCont); -} - -nsresult -nsSelectMoveScrollCommand::DoCommandBrowseWithCaretOn(const char *aCommandName, - nsIDOMWindow *aWindow, nsISelectionController *aSelectionController) -{ - nsresult rv = NS_ERROR_NOT_IMPLEMENTED; - - // cmd_MoveTop/Bottom are used on Window/Unix. They move the caret - // in caret browsing mode. - if (!nsCRT::strcmp(aCommandName, sMoveTopString)) - rv = aSelectionController->CompleteMove(false, false); - else if (!nsCRT::strcmp(aCommandName,sMoveBottomString)) - rv = aSelectionController->CompleteMove(true, false); - // cmd_ScrollTop/Bottom are used on Mac. They do not move the - // caret in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sScrollTopString)) - rv = aSelectionController->CompleteScroll(false); - else if (!nsCRT::strcmp(aCommandName,sScrollBottomString)) - rv = aSelectionController->CompleteScroll(true); - // cmd_MovePageUp/Down are used on Window/Unix. They move the caret - // in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sMovePageUpString)) - rv = aSelectionController->PageMove(false, false); - else if (!nsCRT::strcmp(aCommandName, sMovePageDownString)) - rv = aSelectionController->PageMove(true, false); - // cmd_ScrollPageUp/Down are used on Mac, and for the spacebar on all platforms. - // They do not move the caret in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sScrollPageUpString)) - rv = aSelectionController->ScrollPage(false); - else if (!nsCRT::strcmp(aCommandName, sScrollPageDownString)) - rv = aSelectionController->ScrollPage(true); - else if (!nsCRT::strcmp(aCommandName, sLinePreviousString)) - rv = aSelectionController->LineMove(false, false); - else if (!nsCRT::strcmp(aCommandName, sLineNextString)) - rv = aSelectionController->LineMove(true, false); - else if (!nsCRT::strcmp(aCommandName, sWordPreviousString)) - rv = aSelectionController->WordMove(false, false); - else if (!nsCRT::strcmp(aCommandName, sWordNextString)) - rv = aSelectionController->WordMove(true, false); - else if (!nsCRT::strcmp(aCommandName, sCharPreviousString)) - rv = aSelectionController->CharacterMove(false, false); - else if (!nsCRT::strcmp(aCommandName, sCharNextString)) - rv = aSelectionController->CharacterMove(true, false); - else if (!nsCRT::strcmp(aCommandName, sBeginLineString)) - rv = aSelectionController->IntraLineMove(false, false); - else if (!nsCRT::strcmp(aCommandName, sEndLineString)) - rv = aSelectionController->IntraLineMove(true, false); - - if (NS_SUCCEEDED(rv)) - { - // adjust the focus to the new caret position - nsIFocusManager* fm = nsFocusManager::GetFocusManager(); - if (fm) { - nsCOMPtr<nsIDOMElement> result; - fm->MoveFocus(aWindow, nsnull, nsIFocusManager::MOVEFOCUS_CARET, - nsIFocusManager::FLAG_NOSCROLL, - getter_AddRefs(result)); + for (int i = 0; i < mozilla::ArrayLength(browseCommands); i++) { + bool forward = !strcmp(aCommandName, browseCommands[i].forward); + if (forward || !strcmp(aCommandName, browseCommands[i].reverse)) { + if (caretOn && browseCommands[i].move && + NS_SUCCEEDED((selCont->*(browseCommands[i].move))(forward, false))) { + // adjust the focus to the new caret position + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> result; + fm->MoveFocus(piWindow, nsnull, nsIFocusManager::MOVEFOCUS_CARET, + nsIFocusManager::FLAG_NOSCROLL, + getter_AddRefs(result)); + } + return NS_OK; + } + return (selCont->*(browseCommands[i].scroll))(forward); } } - - return rv; -} - -nsresult -nsSelectMoveScrollCommand::DoCommandBrowseWithCaretOff(const char *aCommandName, nsISelectionController *aSelectionController) -{ - nsresult rv = NS_ERROR_NOT_IMPLEMENTED; - - // cmd_MoveTop/Bottom are used on Window/Unix. They move the caret - // in caret browsing mode. - if (!nsCRT::strcmp(aCommandName, sMoveTopString)) - rv = aSelectionController->CompleteScroll(false); - else if (!nsCRT::strcmp(aCommandName,sMoveBottomString)) - rv = aSelectionController->CompleteScroll(true); - // cmd_ScrollTop/Bottom are used on Mac. They do not move the - // caret in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sScrollTopString)) - rv = aSelectionController->CompleteScroll(false); - else if (!nsCRT::strcmp(aCommandName,sScrollBottomString)) - rv = aSelectionController->CompleteScroll(true); - - // cmd_MovePageUp/Down are used on Window/Unix. They move the caret - // in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sMovePageUpString)) - rv = aSelectionController->ScrollPage(false); - else if (!nsCRT::strcmp(aCommandName, sMovePageDownString)) - rv = aSelectionController->ScrollPage(true); - // cmd_ScrollPageUp/Down are used on Mac. They do not move the - // caret in caret browsing mode. - else if (!nsCRT::strcmp(aCommandName, sScrollPageUpString)) - rv = aSelectionController->ScrollPage(false); - else if (!nsCRT::strcmp(aCommandName, sScrollPageDownString)) - rv = aSelectionController->ScrollPage(true); - - else if (!nsCRT::strcmp(aCommandName, sLinePreviousString)) - rv = aSelectionController->ScrollLine(false); - else if (!nsCRT::strcmp(aCommandName, sLineNextString)) - rv = aSelectionController->ScrollLine(true); - else if (!nsCRT::strcmp(aCommandName, sCharPreviousString)) - rv = aSelectionController->ScrollHorizontal(true); - else if (!nsCRT::strcmp(aCommandName, sCharNextString)) - rv = aSelectionController->ScrollHorizontal(false); - // cmd_beginLine/endLine with caret browsing off - // will act as cmd_moveTop/Bottom - else if (!nsCRT::strcmp(aCommandName, sBeginLineString)) - rv = aSelectionController->CompleteScroll(false); - else if (!nsCRT::strcmp(aCommandName, sEndLineString)) - rv = aSelectionController->CompleteScroll(true); - - return rv; + return NS_ERROR_NOT_IMPLEMENTED; } #if 0 #pragma mark - #endif nsresult -nsSelectCommand::DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow) +nsSelectCommand::DoCommand(const char *aCommandName, nsISupports *aCommandContext) { + nsCOMPtr<nsPIDOMWindow> piWindow(do_QueryInterface(aCommandContext)); nsCOMPtr<nsISelectionController> selCont; - GetSelectionControllerFromWindow(aWindow, getter_AddRefs(selCont)); + GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont)); NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED); nsresult rv = NS_ERROR_NOT_IMPLEMENTED; // These commands are so the browser can use caret navigation key bindings - // Helps with accessibility - aaronl@netscape.com if (!nsCRT::strcmp(aCommandName, sSelectCharPreviousString)) rv = selCont->CharacterMove(false, true); @@ -963,26 +886,30 @@ nsWindowCommandRegistration::RegisterWin { nsresult rv; // XXX rework the macros to use a loop is possible, reducing code size // this set of commands is affected by the 'browse with caret' setting NS_REGISTER_FIRST_COMMAND(nsSelectMoveScrollCommand, sScrollTopString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollBottomString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageUpString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageDownString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineUpString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLineDownString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollLeftString); + NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollRightString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveTopString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMoveBottomString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordPreviousString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sWordNextString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sBeginLineString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sEndLineString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageUpString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sMovePageDownString); - NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageUpString); - NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sScrollPageDownString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLinePreviousString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sLineNextString); NS_REGISTER_NEXT_COMMAND(nsSelectMoveScrollCommand, sCharPreviousString); NS_REGISTER_LAST_COMMAND(nsSelectMoveScrollCommand, sCharNextString); NS_REGISTER_FIRST_COMMAND(nsSelectCommand, sSelectCharPreviousString); NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectCharNextString); NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectWordPreviousString);
--- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -247,23 +247,16 @@ IndexedDatabaseManager::GetOrCreate() nsCOMPtr<nsIObserverService> obs = GetObserverService(); NS_ENSURE_TRUE(obs, nsnull); // We need this callback to know when to shut down all our threads. nsresult rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); NS_ENSURE_SUCCESS(rv, nsnull); - // We don't really need this callback but we want the observer service to - // hold us alive until XPCOM shutdown. That way other consumers can continue - // to use this service until shutdown. - rv = obs->AddObserver(instance, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, - false); - NS_ENSURE_SUCCESS(rv, nsnull); - // Make a lazy thread for any IO we need (like clearing or enumerating the // contents of indexedDB database directories). instance->mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, LazyIdleThread::ManualShutdown); // We need one quota callback object to hand to SQLite. instance->mQuotaCallbackSingleton = new QuotaCallback(); @@ -1219,17 +1212,17 @@ IndexedDatabaseManager::ClearDatabasesFo NS_IMETHODIMP IndexedDatabaseManager::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { // Setting this flag prevents the service from being recreated and prevents // further databases from being created. if (PR_ATOMIC_SET(&gShutdown, 1)) { NS_ERROR("Shutdown more than once?!"); } // Make sure to join with our IO thread. if (NS_FAILED(mIOThread->Shutdown())) { @@ -1274,21 +1267,16 @@ IndexedDatabaseManager::Observe(nsISuppo for (PRUint32 index = 0; index < count; index++) { liveDatabases[index]->Invalidate(); } } return NS_OK; } - if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { - // We're dying now. - return NS_OK; - } - NS_NOTREACHED("Unknown topic!"); return NS_ERROR_UNEXPECTED; } NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable, nsIRunnable) NS_IMETHODIMP
--- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -35,16 +35,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "Key.h" #include "nsIStreamBufferAccess.h" #include "jsdate.h" +#include "nsAlgorithm.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "xpcpublic.h" USING_INDEXEDDB_NAMESPACE /* Here's how we encode keys: @@ -406,38 +407,34 @@ Key::EncodeNumber(double aFloat, PRUint8 *(buffer++) = aType; Float64Union pun; pun.d = aFloat; PRUint64 number = pun.u & PR_UINT64(0x8000000000000000) ? -pun.u : (pun.u | PR_UINT64(0x8000000000000000)); - *reinterpret_cast<PRUint64*>(buffer) = NS_SWAP64(number); + number = NS_SWAP64(number); + memcpy(buffer, &number, sizeof(number)); } // static double Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd) { NS_ASSERTION(*aPos % eMaxType == eFloat || *aPos % eMaxType == eDate, "Don't call me!"); ++aPos; + PRUint64 number = 0; - for (PRInt32 n = 7; n >= 0; --n) { - number <<= 8; - if (aPos < aEnd) { - number |= *(aPos++); - } - else { - number <<= 8 * n; - break; - } - } + memcpy(&number, aPos, NS_MIN<size_t>(sizeof(number), aEnd - aPos)); + number = NS_SWAP64(number); + + aPos += sizeof(number); Float64Union pun; pun.u = number & PR_UINT64(0x8000000000000000) ? (number & ~PR_UINT64(0x8000000000000000)) : -number; return pun.d; }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -64,17 +64,17 @@ interface nsIDOMHTMLCanvasElement; interface nsIDOMEvent; interface nsITransferable; interface nsIQueryContentEventResult; interface nsIDOMWindow; interface nsIDOMBlob; interface nsIDOMFile; interface nsIFile; -[scriptable, uuid(c1fa9c82-acf2-4b27-8ca7-7d1864e606af)] +[scriptable, uuid(15fcceb0-37ea-11e1-b86c-0800200c9a66)] interface nsIDOMWindowUtils : nsISupports { /** * Image animation mode of the window. When this attribute's value * is changed, the implementation should set all images in the window * to the given value. That is, when set to kDontAnimMode, all images * will stop animating. The attribute's value must be one of the * animationMode values from imgIContainer. @@ -833,16 +833,19 @@ interface nsIDOMWindowUtils : nsISupport /** * What type of layer manager the widget associated with this window is * using. "Basic" is unaccelerated; other types are accelerated. Throws an * error if there is no widget associated with this window. */ readonly attribute AString layerManagerType; + void startFrameTimeRecording(); + void stopFrameTimeRecording([optional] out unsigned long frameCount, + [retval, array, size_is(frameCount)] out float frameTime); /** * The DPI of the display */ readonly attribute float displayDPI; /** * Return the outer window with the given ID, if any. Can return null. */
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp +++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp @@ -203,17 +203,17 @@ nsDOMStoragePersistentDB::Init(const nsS // delete the db and try opening again rv = storageFile->Remove(false); NS_ENSURE_SUCCESS(rv, rv); rv = service->OpenDatabase(storageFile, getter_AddRefs(mConnection)); } NS_ENSURE_SUCCESS(rv, rv); rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "PRAGMA temp_store = MEMORY")); + MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY")); NS_ENSURE_SUCCESS(rv, rv); mozStorageTransaction transaction(mConnection, false); // Ensure Gecko 1.9.1 storage table rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE IF NOT EXISTS webappsstore2 (" "scope TEXT, "
--- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -147,16 +147,18 @@ const char* gStringChars[] = { PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gStringChars) == ID_COUNT); enum { PREF_strict = 0, PREF_werror, PREF_relimit, PREF_methodjit, PREF_methodjit_always, + PREF_typeinference, + PREF_jit_hardening, PREF_mem_max, #ifdef JS_GC_ZEAL PREF_gczeal, #endif PREF_COUNT }; @@ -164,16 +166,18 @@ enum { #define JS_OPTIONS_DOT_STR "javascript.options." const char* gPrefsToWatch[] = { JS_OPTIONS_DOT_STR "strict", JS_OPTIONS_DOT_STR "werror", JS_OPTIONS_DOT_STR "relimit", JS_OPTIONS_DOT_STR "methodjit.content", JS_OPTIONS_DOT_STR "methodjit_always", + JS_OPTIONS_DOT_STR "typeinference", + JS_OPTIONS_DOT_STR "jit_hardening", JS_OPTIONS_DOT_STR "mem.max" #ifdef JS_GC_ZEAL , PREF_WORKERS_GCZEAL #endif }; PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gPrefsToWatch) == PREF_COUNT); @@ -208,16 +212,25 @@ PrefCallback(const char* aPrefName, void newOptions |= JSOPTION_RELIMIT; } if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit])) { newOptions |= JSOPTION_METHODJIT; } if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit_always])) { newOptions |= JSOPTION_METHODJIT_ALWAYS; } + if (Preferences::GetBool(gPrefsToWatch[PREF_typeinference])) { + newOptions |= JSOPTION_TYPE_INFERENCE; + } + + // This one is special, it's enabled by default and only needs to be unset. + if (!Preferences::GetBool(gPrefsToWatch[PREF_jit_hardening])) { + newOptions |= JSOPTION_SOFTEN; + } + RuntimeService::SetDefaultJSContextOptions(newOptions); rts->UpdateAllWorkerJSContextOptions(); } #ifdef JS_GC_ZEAL else if (!strcmp(aPrefName, gPrefsToWatch[PREF_gczeal])) { PRInt32 gczeal = Preferences::GetInt(gPrefsToWatch[PREF_gczeal]); RuntimeService::SetDefaultGCZeal(PRUint8(clamped(gczeal, 0, 3))); rts->UpdateAllWorkerGCZeal();
--- a/editor/libeditor/base/tests/test_selection_move_commands.xul +++ b/editor/libeditor/base/tests/test_selection_move_commands.xul @@ -118,16 +118,26 @@ function execTests() { var pageHeight = -root.getBoundingClientRect().top; ok(pageHeight > 0, "cmd_scrollPageDown works"); ok(pageHeight <= 100, "cmd_scrollPageDown doesn't scroll too much"); doCommand("cmd_scrollBottom"); doCommand("cmd_scrollPageUp"); yield; testScrollCommand("cmd_scrollPageUp", root.scrollHeight - 100 - pageHeight); + doCommand("cmd_scrollTop"); + doCommand("cmd_scrollLineDown"); + yield; + var lineHeight = -root.getBoundingClientRect().top; + ok(lineHeight > 0, "Can scroll by lines"); + doCommand("cmd_scrollBottom"); + doCommand("cmd_scrollLineUp"); + yield; + testScrollCommand("cmd_scrollLineUp", root.scrollHeight - 100 - lineHeight); + var runSelectionTests = function(selectWordNextNode, selectWordNextOffset) { testMoveCommand("cmd_moveBottom", body, 23); testMoveCommand("cmd_moveTop", node(0), 0); testSelectCommand("cmd_selectBottom", body, 23); doCommand("cmd_moveBottom"); testSelectCommand("cmd_selectTop", node(0), 0); doCommand("cmd_moveTop");
--- a/editor/libeditor/text/nsTextEditRules.cpp +++ b/editor/libeditor/text/nsTextEditRules.cpp @@ -939,27 +939,23 @@ nsTextEditRules::WillUndo(nsISelection * */ nsresult nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult) { nsresult res = aResult; // if aResult is an error, we return it. if (!aSelection) { return NS_ERROR_NULL_POINTER; } if (NS_SUCCEEDED(res)) { - if (mBogusNode) { - mBogusNode = nsnull; - } + nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot()); + NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); + nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot); + if (node && mEditor->IsMozEditorBogusNode(node)) + mBogusNode = node; else - { - nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot()); - NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); - nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot); - if (node && mEditor->IsMozEditorBogusNode(node)) - mBogusNode = node; - } + mBogusNode = nsnull; } return res; } nsresult nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } @@ -972,40 +968,41 @@ nsTextEditRules::WillRedo(nsISelection * nsresult nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult) { nsresult res = aResult; // if aResult is an error, we return it. if (!aSelection) { return NS_ERROR_NULL_POINTER; } if (NS_SUCCEEDED(res)) { - if (mBogusNode) { - mBogusNode = nsnull; - } - else + nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot()); + NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMNodeList> nodeList; + res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"), + getter_AddRefs(nodeList)); + NS_ENSURE_SUCCESS(res, res); + if (nodeList) { - nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot()); - NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); + PRUint32 len; + nodeList->GetLength(&len); - nsCOMPtr<nsIDOMNodeList> nodeList; - res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"), - getter_AddRefs(nodeList)); - NS_ENSURE_SUCCESS(res, res); - if (nodeList) - { - PRUint32 len; - nodeList->GetLength(&len); - - if (len != 1) return NS_OK; // only in the case of one br could there be the bogus node - nsCOMPtr<nsIDOMNode> node; - nodeList->Item(0, getter_AddRefs(node)); - NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); - if (mEditor->IsMozEditorBogusNode(node)) - mBogusNode = node; + if (len != 1) { + // only in the case of one br could there be the bogus node + mBogusNode = nsnull; + return NS_OK; } + + nsCOMPtr<nsIDOMNode> node; + nodeList->Item(0, getter_AddRefs(node)); + NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); + if (mEditor->IsMozEditorBogusNode(node)) + mBogusNode = node; + else + mBogusNode = nsnull; } } return res; } nsresult nsTextEditRules::WillOutputText(nsISelection *aSelection, const nsAString *aOutputFormat,
--- a/editor/libeditor/text/tests/Makefile.in +++ b/editor/libeditor/text/tests/Makefile.in @@ -71,16 +71,17 @@ include $(topsrcdir)/config/rules.mk # on our editor, and the combinations depend on the system. ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2) _TEST_FILES += \ test_texteditor_keyevent_handling.html \ $(NULL) endif _CHROME_TEST_FILES = \ + test_bug471319.html \ test_bug483651.html \ test_bug636465.xul \ $(NULL) libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir) libs:: $(_CHROME_TEST_FILES)
new file mode 100644 --- /dev/null +++ b/editor/libeditor/text/tests/test_bug471319.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML> +<!-- ***** BEGIN LICENSE BLOCK ***** + - Version: MPL 1.1/GPL 2.0/LGPL 2.1 + - + - The contents of this file are subject to the Mozilla Public License Version + - 1.1 (the "License"); you may not use this file except in compliance with + - the License. You may obtain a copy of the License at + - http://www.mozilla.org/MPL/ + - + - Software distributed under the License is distributed on an "AS IS" basis, + - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + - for the specific language governing rights and limitations under the + - License. + - + - The Original Code is Plaintext Editor Test code + - + - The Initial Developer of the Original Code is + - Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>. + - Portions created by the Initial Developer are Copyright (C) 2011 + - the Initial Developer. All Rights Reserved. + - + - Contributor(s): + - + - + - Alternatively, the contents of this file may be used under the terms of + - either the GNU General Public License Version 2 or later (the "GPL"), or + - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + - in which case the provisions of the GPL or the LGPL are applicable instead + - of those above. If you wish to allow use of your version of this file only + - under the terms of either the GPL or the LGPL, and not to allow others to + - use your version of this file under the terms of the MPL, indicate your + - decision by deleting the provisions above and replace them with the notice + - and other provisions required by the GPL or the LGPL. If you do not delete + - the provisions above, a recipient may use your version of this file under + - the terms of any one of the MPL, the GPL or the LGPL. + - + - ***** END LICENSE BLOCK ***** --> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=471319 +--> + +<head> + <title>Test for Bug 471319</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> + +<body onload="doTest();"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471319">Mozilla Bug 471319</a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + + <pre id="test"> + <script type="application/javascript;version=1.7"> + + /** Test for Bug 471319 **/ + + SimpleTest.waitForExplicitFinish(); + + function doTest() { + let t1 = $("t1"); + let editor = null; + + // Test 1: Undo on an empty editor - the editor should not forget about + // the bogus node + t1.QueryInterface(Components.interfaces.nsIDOMNSEditableElement); + t1Editor = t1.editor; + + // Did the editor recognize the new bogus node? + t1Editor.undo(1); + ok(!t1.value, "<br> still recognized as bogus node on undo"); + + + // Test 2: Redo on an empty editor - the editor should not forget about + // the bogus node + let t2 = $("t2"); + t2.QueryInterface(Components.interfaces.nsIDOMNSEditableElement); + t2Editor = t2.editor; + + // Did the editor recognize the new bogus node? + t2Editor.redo(1); + ok(!t2.value, "<br> still recognized as bogus node on redo"); + + + // Test 3: Undoing a batched transaction where both end points of the + // transaction are the bogus node - the bogus node should still be + // recognized as bogus + t1Editor.transactionManager.beginBatch(); + t1.value = "mozilla"; + t1.value = ""; + t1Editor.transactionManager.endBatch(); + t1Editor.undo(1); + ok(!t1.value, + "recreated <br> from undo transaction recognized as bogus"); + + + // Test 4: Redoing a batched transaction where both end points of the + // transaction are the bogus node - the bogus node should still be + // recognized as bogus + t1Editor.redo(1); + ok(!t1.value, + "recreated <br> from redo transaction recognized as bogus"); + SimpleTest.finish(); + } + </script> + </pre> + + <input type="text" id="t1" /> + <input type="text" id="t2" /> +</body> +</html>
--- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -556,16 +556,41 @@ PlanarYCbCrImage::CopyData(Data& aDest, aData.mCrChannel + i * aData.mCbCrStride, aDest.mCbCrStride); } aDestSize = aData.mPicSize; return buffer; } +void +LayerManager::StartFrameTimeRecording() +{ + mLastFrameTime = TimeStamp::Now(); +} + +void +LayerManager::PostPresent() +{ + if (!mLastFrameTime.IsNull()) { + TimeStamp now = TimeStamp::Now(); + mFrameTimes.AppendElement((now - mLastFrameTime).ToMilliseconds()); + mLastFrameTime = now; + } +} + +nsTArray<float> +LayerManager::StopFrameTimeRecording() +{ + mLastFrameTime = TimeStamp(); + nsTArray<float> result = mFrameTimes; + mFrameTimes.Clear(); + return result; +} + #ifdef MOZ_LAYERS_HAVE_LOG static nsACString& PrintInfo(nsACString& aTo, ShadowLayer* aShadowLayer); void Layer::Dump(FILE* aFile, const char* aPrefix)
--- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -46,16 +46,17 @@ #include "nsISupportsImpl.h" #include "nsAutoPtr.h" #include "gfx3DMatrix.h" #include "gfxColor.h" #include "gfxPattern.h" #include "nsTArray.h" #include "mozilla/gfx/2D.h" +#include "mozilla/TimeStamp.h" #if defined(DEBUG) || defined(PR_LOGGING) # include <stdio.h> // FILE # include "prlog.h" # define MOZ_LAYERS_HAVE_LOG # define MOZ_LAYERS_LOG(_args) \ PR_LOG(LayerManager::GetLog(), PR_LOG_DEBUG, _args) #else @@ -507,16 +508,21 @@ public: */ void Log(const char* aPrefix=""); /** * Log information about just this layer manager itself to the NSPR * log (if enabled for "Layers"). */ void LogSelf(const char* aPrefix=""); + void StartFrameTimeRecording(); + nsTArray<float> StopFrameTimeRecording(); + + void PostPresent(); + static bool IsLogEnabled(); static PRLogModuleInfo* GetLog() { return sLog; } bool IsCompositingCheap(LayerManager::LayersBackend aBackend) { return LAYERS_BASIC != aBackend; } virtual bool IsCompositingCheap() { return true; } @@ -527,16 +533,19 @@ protected: bool mSnapEffectiveTransforms; // Print interesting information about this into aTo. Internally // used to implement Dump*() and Log*(). virtual nsACString& PrintInfo(nsACString& aTo, const char* aPrefix); static void InitLog(); static PRLogModuleInfo* sLog; +private: + TimeStamp mLastFrameTime; + nsTArray<float> mFrameTimes; }; class ThebesLayer; /** * A Layer represents anything that can be rendered onto a destination * surface. */
--- a/gfx/layers/d3d10/LayerManagerD3D10.cpp +++ b/gfx/layers/d3d10/LayerManagerD3D10.cpp @@ -773,16 +773,17 @@ LayerManagerD3D10::Render() ShadowLayerForwarder::EndTransaction(&replies); // We expect only 1 reply, but might get none if the parent // process crashed swap(mBackBuffer, mRemoteFrontBuffer); } else { mSwapChain->Present(0, 0); } + LayerManager::PostPresent(); } void LayerManagerD3D10::PaintToTarget() { nsRefPtr<ID3D10Texture2D> backBuf; mSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void**)backBuf.StartAssignment());
--- a/gfx/layers/d3d9/LayerManagerD3D9.cpp +++ b/gfx/layers/d3d9/LayerManagerD3D9.cpp @@ -349,16 +349,17 @@ LayerManagerD3D9::Render() device()->EndScene(); if (!mTarget) { const nsIntRect *r; for (nsIntRegionRectIterator iter(mClippingRegion); (r = iter.Next()) != nsnull;) { mSwapChain->Present(*r); } + LayerManager::PostPresent(); } else { PaintToTarget(); } } void LayerManagerD3D9::SetupPipeline() {
--- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -814,16 +814,17 @@ LayerManagerOGL::Render() } if (sDrawFPS) { mFPS.DrawFPS(mGLContext, GetCopy2DProgram()); } if (mGLContext->IsDoubleBuffered()) { mGLContext->SwapBuffers(); + LayerManager::PostPresent(); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); return; } mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
--- a/gfx/src/nsFontMetrics.cpp +++ b/gfx/src/nsFontMetrics.cpp @@ -36,48 +36,54 @@ * * ***** END LICENSE BLOCK ***** */ #include "nsFontMetrics.h" #include "nsBoundingMetrics.h" #include "nsRenderingContext.h" #include "nsDeviceContext.h" #include "nsStyleConsts.h" -#include "gfxTextRunCache.h" namespace { -class AutoTextRun : public gfxTextRunCache::AutoTextRun { +class AutoTextRun { public: AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC, const char* aString, PRInt32 aLength) - : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun( + { + mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( reinterpret_cast<const PRUint8*>(aString), aLength, - aMetrics->GetThebesFontGroup(), aRC->ThebesContext(), + aRC->ThebesContext(), aMetrics->AppUnitsPerDevPixel(), - ComputeFlags(aMetrics))) - {} + ComputeFlags(aMetrics)); + } AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC, const PRUnichar* aString, PRInt32 aLength) - : gfxTextRunCache::AutoTextRun(gfxTextRunCache::MakeTextRun( - aString, aLength, aMetrics->GetThebesFontGroup(), + { + mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( + aString, aLength, aRC->ThebesContext(), aMetrics->AppUnitsPerDevPixel(), - ComputeFlags(aMetrics))) - {} + ComputeFlags(aMetrics)); + } + + gfxTextRun *get() { return mTextRun; } + gfxTextRun *operator->() { return mTextRun; } private: static PRUint32 ComputeFlags(nsFontMetrics* aMetrics) { PRUint32 flags = 0; if (aMetrics->GetTextRunRTL()) { flags |= gfxTextRunFactory::TEXT_IS_RTL; } return flags; } + + nsAutoPtr<gfxTextRun> mTextRun; }; class StubPropertyProvider : public gfxTextRun::PropertyProvider { public: virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, bool* aBreakBefore) { NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); }
--- a/gfx/tests/gfxFontSelectionTest.cpp +++ b/gfx/tests/gfxFontSelectionTest.cpp @@ -40,17 +40,16 @@ #include "nsString.h" #include "nsDependentString.h" #include "mozilla/Preferences.h" #include "gfxContext.h" #include "gfxFont.h" #include "gfxPlatform.h" -#include "gfxTextRunWordCache.h" #include "gfxFontTest.h" #if defined(XP_MACOSX) #include "gfxTestCocoaHelper.h" #endif #ifdef MOZ_WIDGET_GTK2 @@ -61,18 +60,16 @@ using namespace mozilla; enum { S_UTF8 = 0, S_ASCII = 1 }; class FrameTextRunCache; -static gfxTextRunWordCache *gTextRunCache; - struct LiteralArray { LiteralArray (unsigned long l1) { data.AppendElement(l1); } LiteralArray (unsigned long l1, unsigned long l2) { data.AppendElement(l1); data.AppendElement(l2); } @@ -299,21 +296,21 @@ RunTest (TestEntry *test, gfxContext *ct PRUint32 flags = gfxTextRunFactory::TEXT_IS_PERSISTENT; if (test->isRTL) { flags |= gfxTextRunFactory::TEXT_IS_RTL; } PRUint32 length; if (test->stringType == S_ASCII) { flags |= gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_8BIT; length = strlen(test->string); - textRun = gfxTextRunWordCache::MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, fontGroup, ¶ms, flags); + textRun = fontGroup->MakeTextRun(reinterpret_cast<const PRUint8*>(test->string), length, ¶ms, flags); } else { NS_ConvertUTF8toUTF16 str(nsDependentCString(test->string)); length = str.Length(); - textRun = gfxTextRunWordCache::MakeTextRun(str.get(), length, fontGroup, ¶ms, flags); + textRun = fontGroup->MakeTextRun(str.get(), length, ¶ms, flags); } gfxFontTestStore::NewStore(); textRun->Draw(ctx, gfxPoint(0,0), 0, length, nsnull, nsnull); gfxFontTestStore *s = gfxFontTestStore::CurrentStore(); gTextRunCache->RemoveTextRun(textRun); @@ -343,18 +340,16 @@ main (int argc, char **argv) { nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull); if (NS_FAILED(rv)) return -1; rv = gfxPlatform::Init(); if (NS_FAILED(rv)) return -1; - gTextRunCache = new gfxTextRunWordCache(); - // let's get all the xpcom goop out of the system fflush (stderr); fflush (stdout); // don't need to query, we might need to set up some prefs later if (0) { nsresult rv;
--- a/gfx/tests/gfxWordCacheTest.cpp +++ b/gfx/tests/gfxWordCacheTest.cpp @@ -43,18 +43,16 @@ #include "prinrval.h" #include "gfxContext.h" #include "gfxFont.h" #include "gfxPlatform.h" #include "gfxFontTest.h" -#include "gfxTextRunWordCache.h" - #if defined(XP_MACOSX) #include "gfxTestCocoaHelper.h" #endif #ifdef MOZ_WIDGET_GTK2 #include "gtk/gtk.h" #endif @@ -73,17 +71,16 @@ public: ~FrameTextRunCache() { AgeAllGenerations(); } void RemoveFromCache(gfxTextRun* aTextRun) { if (aTextRun->GetExpirationState()->IsTracked()) { RemoveObject(aTextRun); } - gfxTextRunWordCache::RemoveTextRun(aTextRun); } // This gets called when the timeout has expired on a gfxTextRun virtual void NotifyExpired(gfxTextRun* aTextRun) { RemoveFromCache(aTextRun); delete aTextRun; } }; @@ -94,18 +91,17 @@ MakeTextRun(const PRUnichar *aText, PRUi PRUint32 aFlags) { nsAutoPtr<gfxTextRun> textRun; if (aLength == 0) { textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags); } else if (aLength == 1 && aText[0] == ' ') { textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags); } else { - textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup, - aParams, aFlags); + textRun = aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags); } if (!textRun) return nsnull; nsresult rv = gTextRuns->AddObject(textRun); if (NS_FAILED(rv)) { gTextRuns->RemoveFromCache(textRun); return nsnull; }
--- a/gfx/thebes/Makefile.in +++ b/gfx/thebes/Makefile.in @@ -70,18 +70,16 @@ EXPORTS = \ gfxPoint3D.h \ gfxPointH3D.h \ gfxQuad.h \ gfxQuaternion.h \ gfxRect.h \ gfxSkipChars.h \ gfxTeeSurface.h \ gfxTypes.h \ - gfxTextRunCache.h \ - gfxTextRunWordCache.h \ gfxUnicodeProperties.h \ gfxUtils.h \ gfxUserFontSet.h \ nsCoreAnimationSupport.h \ gfxSharedImageSurface.h \ $(NULL) ifeq ($(MOZ_WIDGET_TOOLKIT),android) @@ -197,18 +195,16 @@ CPPSRCS = \ gfxMatrix.cpp \ gfxPath.cpp \ gfxPattern.cpp \ gfxPlatform.cpp \ gfxPlatformFontList.cpp \ gfxRect.cpp \ gfxSkipChars.cpp \ gfxTeeSurface.cpp \ - gfxTextRunCache.cpp \ - gfxTextRunWordCache.cpp \ gfxUserFontSet.cpp \ gfxUtils.cpp \ gfxUnicodeProperties.cpp \ gfxScriptItemizer.cpp \ gfxHarfBuzzShaper.cpp \ gfxSharedImageSurface.cpp \ $(NULL)
--- a/gfx/thebes/gfxASurface.h +++ b/gfx/thebes/gfxASurface.h @@ -52,19 +52,34 @@ struct nsIntPoint; struct nsIntRect; /** * A surface is something you can draw on. Instantiate a subclass of this * abstract class, and use gfxContext to draw on this surface. */ class THEBES_API gfxASurface { public: +#ifdef MOZILLA_INTERNAL_API nsrefcnt AddRef(void); nsrefcnt Release(void); + // These functions exist so that browsercomps can refcount a gfxASurface + virtual nsresult AddRefExternal(void) + { + return AddRef(); + } + virtual nsresult ReleaseExternal(void) + { + return Release(); + } +#else + virtual nsresult AddRef(void); + virtual nsresult Release(void); +#endif + public: /** * The format for an image surface. For all formats with alpha data, 0 * means transparent, 1 or 255 means fully opaque. */ typedef enum { ImageFormatARGB32, ///< ARGB data in native endianness, using premultiplied alpha ImageFormatRGB24, ///< xRGB data in native endianness
--- a/gfx/thebes/gfxCoreTextShaper.cpp +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -103,79 +103,69 @@ gfxCoreTextShaper::~gfxCoreTextShaper() ::CFRelease(mAttributesDict); } if (mCTFont) { ::CFRelease(mCTFont); } } bool -gfxCoreTextShaper::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +gfxCoreTextShaper::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText) { - // aRunStart and aRunLength define the section of the textRun and of aString - // that is to be drawn with this particular font - - bool disableLigatures = (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; - // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out. - bool isRTL = aTextRun->IsRightToLeft(); + bool isRightToLeft = aShapedWord->IsRightToLeft(); + PRUint32 length = aShapedWord->Length(); // we need to bidi-wrap the text if the run is RTL, // or if it is an LTR run but may contain (overridden) RTL chars - bool bidiWrap = isRTL; - if (!bidiWrap && (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) == 0) { + bool bidiWrap = isRightToLeft; + if (!bidiWrap && !aShapedWord->TextIs8Bit()) { + const PRUnichar *text = aShapedWord->TextUnicode(); PRUint32 i; - for (i = aRunStart; i < aRunStart + aRunLength; ++i) { - if (gfxFontUtils::PotentialRTLChar(aString[i])) { + for (i = 0; i < length; ++i) { + if (gfxFontUtils::PotentialRTLChar(text[i])) { bidiWrap = true; break; } } } // If there's a possibility of any bidi, we wrap the text with direction overrides // to ensure neutrals or characters that were bidi-overridden in HTML behave properly. const UniChar beginLTR[] = { 0x202d, 0x20 }; const UniChar beginRTL[] = { 0x202e, 0x20 }; const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; PRUint32 startOffset; CFStringRef stringObj; if (bidiWrap) { - startOffset = isRTL ? - sizeof(beginRTL) / sizeof(beginRTL[0]) : sizeof(beginLTR) / sizeof(beginLTR[0]); + startOffset = isRightToLeft ? + mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR); CFMutableStringRef mutableString = ::CFStringCreateMutable(kCFAllocatorDefault, - aRunLength + startOffset + - sizeof(endBidiWrap) / sizeof(endBidiWrap[0])); + length + startOffset + mozilla::ArrayLength(endBidiWrap)); ::CFStringAppendCharacters(mutableString, - isRTL ? beginRTL : beginLTR, + isRightToLeft ? beginRTL : beginLTR, startOffset); - ::CFStringAppendCharacters(mutableString, - aString + aRunStart, aRunLength); + ::CFStringAppendCharacters(mutableString, aText, length); ::CFStringAppendCharacters(mutableString, - endBidiWrap, - sizeof(endBidiWrap) / sizeof(endBidiWrap[0])); + endBidiWrap, mozilla::ArrayLength(endBidiWrap)); stringObj = mutableString; } else { startOffset = 0; stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - aString + aRunStart, - aRunLength, + aText, length, kCFAllocatorNull); } CFDictionaryRef attrObj; - if (disableLigatures) { + if (aShapedWord->DisableLigatures()) { // For letterspacing (or maybe other situations) we need to make a copy of the CTFont // with the ligature feature disabled CTFontRef ctFont = CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont)); attrObj = ::CFDictionaryCreate(kCFAllocatorDefault, (const void**) &kCTFontAttributeName, @@ -204,69 +194,64 @@ gfxCoreTextShaper::InitTextRun(gfxContex CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); PRUint32 numRuns = ::CFArrayGetCount(glyphRuns); // Iterate through the glyph runs. // Note that this includes the bidi wrapper, so we have to be careful // not to include the extra glyphs from there bool success = true; for (PRUint32 runIndex = 0; runIndex < numRuns; runIndex++) { - CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); - if (SetGlyphsFromRun(aTextRun, aCTRun, startOffset, - aRunStart, aRunLength) != NS_OK) { + CTRunRef aCTRun = + (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); + if (SetGlyphsFromRun(aShapedWord, aCTRun, startOffset) != NS_OK) { success = false; break; } } ::CFRelease(line); return success; } #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data; // some testing indicates that 90%+ of glyph runs will fit // without requiring a separate allocation nsresult -gfxCoreTextShaper::SetGlyphsFromRun(gfxTextRun *aTextRun, +gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedWord *aShapedWord, CTRunRef aCTRun, - PRInt32 aStringOffset, // offset in the string used to build the CTLine - PRInt32 aRunStart, // starting offset of this font run in the gfxTextRun - PRInt32 aRunLength) // length of this font run in characters + PRInt32 aStringOffset) { - // The textRun has been bidi-wrapped; aStringOffset is the number + // The word has been bidi-wrapped; aStringOffset is the number // of chars at the beginning of the CTLine that we should skip. - // aRunStart and aRunLength define the range of characters - // within the textRun that are "real" data we need to handle. // aCTRun is a glyph run from the CoreText layout process. - bool isLTR = !aTextRun->IsRightToLeft(); - PRInt32 direction = isLTR ? 1 : -1; + PRInt32 direction = aShapedWord->IsRightToLeft() ? -1 : 1; PRInt32 numGlyphs = ::CTRunGetGlyphCount(aCTRun); if (numGlyphs == 0) { return NS_OK; } + PRInt32 wordLength = aShapedWord->Length(); + // character offsets get really confusing here, as we have to keep track of // (a) the text in the actual textRun we're constructing - // (b) the "font run" being rendered with the current font, defined by aRunStart and aRunLength - // parameters to InitTextRun // (c) the string that was handed to CoreText, which contains the text of the font run // plus directional-override padding // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line // (but may extend beyond the actual font run into the bidi wrapping text). // aStringOffset tells us how many initial characters of the line to ignore. // get the source string range within the CTLine's text CFRange stringRange = ::CTRunGetStringRange(aCTRun); // skip the run if it is entirely outside the actual range of the font run if (stringRange.location - aStringOffset + stringRange.length <= 0 || - stringRange.location - aStringOffset >= aRunLength) { + stringRange.location - aStringOffset >= wordLength) { return NS_OK; } // retrieve the laid-out glyph data from the CTRun nsAutoArrayPtr<CGGlyph> glyphsArray; nsAutoArrayPtr<CGPoint> positionsArray; nsAutoArrayPtr<CFIndex> glyphToCharArray; const CGGlyph* glyphs = NULL; @@ -312,17 +297,16 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get()); glyphToChar = glyphToCharArray.get(); } double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), NULL, NULL, NULL); nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs; gfxTextRun::CompressedGlyph g; - const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph // to a source text character; we also need the charindex-to-glyphindex mapping to // find the glyph for a given char. Note that some chars may not map to any glyph // (ligature continuations), and some may map to several glyphs (eg Indic split vowels). // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we // record the last glyph index for cases where the char maps to several glyphs, // so that our clumping will include all the glyph fragments for the character. @@ -352,31 +336,33 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT // and extend it until it includes the character associated with the first glyph; // we also extend it as long as there are "holes" in the range of glyphs. So we // will eventually have a contiguous sequence of characters, starting at the beginning // of the range, that map to a contiguous sequence of glyphs, starting at the beginning // of the glyph array. That's a clump; then we update the starting positions and repeat. // // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. // - // This may find characters that fall outside the range aRunStart:aRunLength, + + // This may find characters that fall outside the range 0:wordLength, // so we won't necessarily use everything we find here. + bool isRightToLeft = aShapedWord->IsRightToLeft(); PRInt32 glyphStart = 0; // looking for a clump that starts at this glyph index - PRInt32 charStart = isLTR ? - 0 : stringRange.length-1; // and this char index (in the stringRange of the glyph run) + PRInt32 charStart = isRightToLeft ? + stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run) while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for bool inOrder = true; PRInt32 charEnd = glyphToChar[glyphStart] - stringRange.location; NS_ASSERTION(charEnd >= 0 && charEnd < stringRange.length, "glyph-to-char mapping points outside string range"); PRInt32 glyphEnd = glyphStart; - PRInt32 charLimit = isLTR ? stringRange.length : -1; + PRInt32 charLimit = isRightToLeft ? -1 : stringRange.length; do { // This is normally executed once for each iteration of the outer loop, // but in unusual cases where the character/glyph association is complex, // the initial character range might correspond to a non-contiguous // glyph range with "holes" in it. If so, we will repeat this loop to // extend the character range until we have a contiguous glyph sequence. NS_ASSERTION((direction > 0 && charEnd < charLimit) || (direction < 0 && charEnd > charLimit), @@ -405,93 +391,95 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT // check whether all glyphs in the range are associated with the characters // in our clump; if not, we have a discontinuous range, and should extend it // unless we've reached the end of the text bool allGlyphsAreWithinCluster = true; PRInt32 prevGlyphCharIndex = charStart; for (PRInt32 i = glyphStart; i < glyphEnd; ++i) { PRInt32 glyphCharIndex = glyphToChar[i] - stringRange.location; - if (isLTR) { + if (isRightToLeft) { + if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex > prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } else { if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { allGlyphsAreWithinCluster = false; break; } if (glyphCharIndex < prevGlyphCharIndex) { inOrder = false; } prevGlyphCharIndex = glyphCharIndex; - } else { - if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { - allGlyphsAreWithinCluster = false; - break; - } - if (glyphCharIndex > prevGlyphCharIndex) { - inOrder = false; - } - prevGlyphCharIndex = glyphCharIndex; } } if (allGlyphsAreWithinCluster) { break; } } while (charEnd != charLimit); NS_ASSERTION(glyphStart < glyphEnd, "character/glyph clump contains no glyphs!"); NS_ASSERTION(charStart != charEnd, "character/glyph contains no characters!"); // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), // and endCharIndex to the limit (position beyond the last char), // adjusting for the offset of the stringRange relative to the textRun. PRInt32 baseCharIndex, endCharIndex; - if (isLTR) { + if (isRightToLeft) { + while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { + charEnd--; + } + baseCharIndex = charEnd + stringRange.location - aStringOffset + 1; + endCharIndex = charStart + stringRange.location - aStringOffset + 1; + } else { while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { charEnd++; } - baseCharIndex = charStart + stringRange.location - aStringOffset + aRunStart; - endCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart; - } else { - while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { - charEnd--; - } - baseCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart + 1; - endCharIndex = charStart + stringRange.location - aStringOffset + aRunStart + 1; + baseCharIndex = charStart + stringRange.location - aStringOffset; + endCharIndex = charEnd + stringRange.location - aStringOffset; } // Then we check if the clump falls outside our actual string range; if so, just go to the next. - if (endCharIndex <= aRunStart || baseCharIndex >= aRunStart + aRunLength) { + if (endCharIndex <= 0 || baseCharIndex >= wordLength) { glyphStart = glyphEnd; charStart = charEnd; continue; } - // Ensure we won't try to go beyond the valid length of the textRun's text - baseCharIndex = NS_MAX(baseCharIndex, aRunStart); - endCharIndex = NS_MIN(endCharIndex, aRunStart + aRunLength); + // Ensure we won't try to go beyond the valid length of the word's text + baseCharIndex = NS_MAX(baseCharIndex, 0); + endCharIndex = NS_MIN(endCharIndex, wordLength); // Now we're ready to set the glyph info in the textRun; measure the glyph width // of the first (perhaps only) glyph, to see if it is "Simple" + PRInt32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit(); double toNextGlyph; if (glyphStart < numGlyphs-1) { toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; } else { toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; } PRInt32 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit); // Check if it's a simple one-to-one mapping PRInt32 glyphsInClump = glyphEnd - glyphStart; if (glyphsInClump == 1 && gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && - aTextRun->IsClusterStart(baseCharIndex) && + aShapedWord->IsClusterStart(baseCharIndex) && positions[glyphStart].y == 0.0) { - aTextRun->SetSimpleGlyph(baseCharIndex, - g.SetSimpleGlyph(advance, glyphs[glyphStart])); + aShapedWord->SetSimpleGlyph(baseCharIndex, + g.SetSimpleGlyph(advance, + glyphs[glyphStart])); } else { // collect all glyphs in a list to be assigned to the first char; // there must be at least one in the clump, and we already measured its advance, // hence the placement of the loop-exit test and the measurement of the next glyph while (1) { gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement(); details->mGlyphID = glyphs[glyphStart]; details->mXOffset = 0; @@ -504,28 +492,28 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxT toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; } else { toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; } advance = PRInt32(toNextGlyph * appUnitsPerDevUnit); } gfxTextRun::CompressedGlyph g; - g.SetComplex(aTextRun->IsClusterStart(baseCharIndex), + g.SetComplex(aShapedWord->IsClusterStart(baseCharIndex), true, detailedGlyphs.Length()); - aTextRun->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements()); + aShapedWord->SetGlyphs(baseCharIndex, g, detailedGlyphs.Elements()); detailedGlyphs.Clear(); } // the rest of the chars in the group are ligature continuations, no associated glyphs - while (++baseCharIndex != endCharIndex && baseCharIndex < aRunStart + aRunLength) { - g.SetComplex(inOrder && aTextRun->IsClusterStart(baseCharIndex), + while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { + g.SetComplex(inOrder && aShapedWord->IsClusterStart(baseCharIndex), false, 0); - aTextRun->SetGlyphs(baseCharIndex, g, nsnull); + aShapedWord->SetGlyphs(baseCharIndex, g, nsnull); } glyphStart = glyphEnd; charStart = charEnd; } return NS_OK; }
--- a/gfx/thebes/gfxCoreTextShaper.h +++ b/gfx/thebes/gfxCoreTextShaper.h @@ -52,35 +52,30 @@ class gfxMacFont; class gfxCoreTextShaper : public gfxFontShaper { public: gfxCoreTextShaper(gfxMacFont *aFont); virtual ~gfxCoreTextShaper(); - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText); // clean up static objects that may have been cached static void Shutdown(); protected: CTFontRef mCTFont; CFDictionaryRef mAttributesDict; - nsresult SetGlyphsFromRun(gfxTextRun *aTextRun, + nsresult SetGlyphsFromRun(gfxShapedWord *aShapedWord, CTRunRef aCTRun, - PRInt32 aStringOffset, - PRInt32 aLayoutStart, - PRInt32 aLayoutLength); + PRInt32 aStringOffset); CTFontRef CreateCTFontWithDisabledLigatures(CGFloat aSize); static void CreateDefaultFeaturesDescriptor(); static CTFontDescriptorRef GetDefaultFeaturesDescriptor() { if (sDefaultFeaturesDescriptor == NULL) { CreateDefaultFeaturesDescriptor();
--- a/gfx/thebes/gfxDWriteShaper.cpp +++ b/gfx/thebes/gfxDWriteShaper.cpp @@ -39,261 +39,219 @@ #include "gfxWindowsPlatform.h" #include <dwrite.h> #include "gfxDWriteTextAnalysis.h" #include "nsCRT.h" -#define MAX_RANGE_LENGTH 25000 - bool -gfxDWriteShaper::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +gfxDWriteShaper::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString) { HRESULT hr; // TODO: Handle TEST_DISABLE_OPTIONAL_LIGATURES DWRITE_READING_DIRECTION readingDirection = - aTextRun->IsRightToLeft() + aShapedWord->IsRightToLeft() ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; gfxDWriteFont *font = static_cast<gfxDWriteFont*>(mFont); - gfxTextRun::CompressedGlyph g; - - nsRefPtr<IDWriteTextAnalyzer> analyzer; + gfxShapedWord::CompressedGlyph g; - hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()-> - CreateTextAnalyzer(getter_AddRefs(analyzer)); - if (FAILED(hr)) { + IDWriteTextAnalyzer *analyzer = + gfxWindowsPlatform::GetPlatform()->GetDWriteAnalyzer(); + if (!analyzer) { return false; } /** - * There's an internal 16-bit limit on some things inside the analyzer. - * to be on the safe side here we split up any ranges over MAX_RANGE_LENGTH - * characters. - * TODO: Figure out what exactly is going on, and what is a safe number, and - * why. + * There's an internal 16-bit limit on some things inside the analyzer, + * but we never attempt to shape a word longer than 64K characters + * in a single gfxShapedWord, so we cannot exceed that limit. */ - bool result = true; - UINT32 rangeOffset = 0; - while (rangeOffset < aRunLength) { - PRUint32 rangeLen = NS_MIN<PRUint32>(aRunLength - rangeOffset, - MAX_RANGE_LENGTH); - if (rangeOffset + rangeLen < aRunLength) { - // Iterate backwards to find a decent place to break shaping: - // look for a whitespace char, and if that's not found then - // at least a char where IsClusterStart is true. - // However, we never iterate back further than half-way; - // if a decent break is not found by then, we just chop anyway - // at the original range length. - PRUint32 adjRangeLen = 0; - const PRUnichar *rangeStr = aString + aRunStart + rangeOffset; - for (PRUint32 i = rangeLen; i > MAX_RANGE_LENGTH / 2; i--) { - if (nsCRT::IsAsciiSpace(rangeStr[i])) { - adjRangeLen = i; - break; - } - if (adjRangeLen == 0 && - aTextRun->IsClusterStart(aRunStart + rangeOffset + i)) { - adjRangeLen = i; - } - } - if (adjRangeLen != 0) { - rangeLen = adjRangeLen; - } - } + UINT32 length = aShapedWord->Length(); + + TextAnalysis analysis(aString, length, NULL, readingDirection); + TextAnalysis::Run *runHead; + hr = analysis.GenerateResults(analyzer, &runHead); + + if (FAILED(hr)) { + NS_WARNING("Analyzer failed to generate results."); + return false; + } + + PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit(); - PRUint32 rangeStart = aRunStart + rangeOffset; - rangeOffset += rangeLen; - TextAnalysis analysis(aString + rangeStart, rangeLen, - NULL, - readingDirection); - TextAnalysis::Run *runHead; - DWRITE_LINE_BREAKPOINT *linebreaks; - hr = analysis.GenerateResults(analyzer, &runHead, &linebreaks); - - if (FAILED(hr)) { - NS_WARNING("Analyzer failed to generate results."); - result = false; - break; - } - - PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit(); - - UINT32 maxGlyphs = 0; + UINT32 maxGlyphs = 0; trymoreglyphs: - if ((PR_UINT32_MAX - 3 * rangeLen / 2 + 16) < maxGlyphs) { - // This isn't going to work, we're going to cross the UINT32 upper - // limit. Next range it is. - continue; - } - maxGlyphs += 3 * rangeLen / 2 + 16; + if ((PR_UINT32_MAX - 3 * length / 2 + 16) < maxGlyphs) { + // This isn't going to work, we're going to cross the UINT32 upper + // limit. Give up. + NS_WARNING("Shaper needs to generate more than 2^32 glyphs?!"); + return false; + } + maxGlyphs += 3 * length / 2 + 16; - nsAutoTArray<UINT16, 400> clusters; - nsAutoTArray<UINT16, 400> indices; - nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties; - nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties; - if (!clusters.SetLength(rangeLen) || - !indices.SetLength(maxGlyphs) || - !textProperties.SetLength(maxGlyphs) || - !glyphProperties.SetLength(maxGlyphs)) { - continue; - } + nsAutoTArray<UINT16, 400> clusters; + nsAutoTArray<UINT16, 400> indices; + nsAutoTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties; + nsAutoTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties; + if (!clusters.SetLength(length) || + !indices.SetLength(maxGlyphs) || + !textProperties.SetLength(maxGlyphs) || + !glyphProperties.SetLength(maxGlyphs)) { + NS_WARNING("Shaper failed to allocate memory."); + return false; + } - UINT32 actualGlyphs; + UINT32 actualGlyphs; - hr = analyzer->GetGlyphs(aString + rangeStart, rangeLen, + hr = analyzer->GetGlyphs(aString, length, font->GetFontFace(), FALSE, readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT, &runHead->mScript, NULL, NULL, NULL, NULL, 0, maxGlyphs, clusters.Elements(), textProperties.Elements(), indices.Elements(), glyphProperties.Elements(), &actualGlyphs); - if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { - // We increase the amount of glyphs and try again. - goto trymoreglyphs; - } - if (FAILED(hr)) { - NS_WARNING("Analyzer failed to get glyphs."); - result = false; - break; - } + if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { + // We increase the amount of glyphs and try again. + goto trymoreglyphs; + } + if (FAILED(hr)) { + NS_WARNING("Analyzer failed to get glyphs."); + return false; + } + + WORD gID = indices[0]; + nsAutoTArray<FLOAT, 400> advances; + nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets; + if (!advances.SetLength(actualGlyphs) || + !glyphOffsets.SetLength(actualGlyphs)) { + NS_WARNING("Shaper failed to allocate memory."); + return false; + } - WORD gID = indices[0]; - nsAutoTArray<FLOAT, 400> advances; - nsAutoTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets; - if (!advances.SetLength(actualGlyphs) || - !glyphOffsets.SetLength(actualGlyphs)) { + if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) { + hr = analyzer->GetGdiCompatibleGlyphPlacements( + aString, + clusters.Elements(), + textProperties.Elements(), + length, + indices.Elements(), + glyphProperties.Elements(), + actualGlyphs, + font->GetFontFace(), + font->GetAdjustedSize(), + 1.0, + nsnull, + FALSE, + FALSE, + FALSE, + &runHead->mScript, + NULL, + NULL, + NULL, + 0, + advances.Elements(), + glyphOffsets.Elements()); + } else { + hr = analyzer->GetGlyphPlacements(aString, + clusters.Elements(), + textProperties.Elements(), + length, + indices.Elements(), + glyphProperties.Elements(), + actualGlyphs, + font->GetFontFace(), + font->GetAdjustedSize(), + FALSE, + FALSE, + &runHead->mScript, + NULL, + NULL, + NULL, + 0, + advances.Elements(), + glyphOffsets.Elements()); + } + if (FAILED(hr)) { + NS_WARNING("Analyzer failed to get glyph placements."); + return false; + } + + nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs; + + for (unsigned int c = 0; c < length; c++) { + PRUint32 k = clusters[c]; + PRUint32 absC = c; + + if (c > 0 && k == clusters[c - 1]) { + g.SetComplex(aShapedWord->IsClusterStart(absC), false, 0); + aShapedWord->SetGlyphs(absC, g, nsnull); + // This is a cluster continuation. No glyph here. continue; } - if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) { - hr = analyzer->GetGdiCompatibleGlyphPlacements( - aString + rangeStart, - clusters.Elements(), - textProperties.Elements(), - rangeLen, - indices.Elements(), - glyphProperties.Elements(), - actualGlyphs, - font->GetFontFace(), - font->GetAdjustedSize(), - 1.0, - nsnull, - FALSE, - FALSE, - FALSE, - &runHead->mScript, - NULL, - NULL, - NULL, - 0, - advances.Elements(), - glyphOffsets.Elements()); + // Count glyphs for this character + PRUint32 glyphCount = actualGlyphs - k; + PRUint32 nextClusterOffset; + for (nextClusterOffset = c + 1; + nextClusterOffset < length; ++nextClusterOffset) { + if (clusters[nextClusterOffset] > k) { + glyphCount = clusters[nextClusterOffset] - k; + break; + } + } + PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel); + if (glyphCount == 1 && advance >= 0 && + glyphOffsets[k].advanceOffset == 0 && + glyphOffsets[k].ascenderOffset == 0 && + aShapedWord->IsClusterStart(absC) && + gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) && + gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(indices[k])) { + aShapedWord->SetSimpleGlyph(absC, + g.SetSimpleGlyph(advance, + indices[k])); } else { - hr = analyzer->GetGlyphPlacements(aString + rangeStart, - clusters.Elements(), - textProperties.Elements(), - rangeLen, - indices.Elements(), - glyphProperties.Elements(), - actualGlyphs, - font->GetFontFace(), - font->GetAdjustedSize(), - FALSE, - FALSE, - &runHead->mScript, - NULL, - NULL, - NULL, - 0, - advances.Elements(), - glyphOffsets.Elements()); - } - if (FAILED(hr)) { - NS_WARNING("Analyzer failed to get glyph placements."); - result = false; - break; - } - - nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs; - - for (unsigned int c = 0; c < rangeLen; c++) { - PRUint32 k = clusters[c]; - PRUint32 absC = rangeStart + c; - - if (c > 0 && k == clusters[c - 1]) { - g.SetComplex(aTextRun->IsClusterStart(absC), false, 0); - aTextRun->SetGlyphs(absC, g, nsnull); - // This is a cluster continuation. No glyph here. - continue; - } - - // Count glyphs for this character - PRUint32 glyphCount = actualGlyphs - k; - PRUint32 nextClusterOffset; - for (nextClusterOffset = c + 1; - nextClusterOffset < rangeLen; ++nextClusterOffset) { - if (clusters[nextClusterOffset] > k) { - glyphCount = clusters[nextClusterOffset] - k; - break; + if (detailedGlyphs.Length() < glyphCount) { + if (!detailedGlyphs.AppendElements( + glyphCount - detailedGlyphs.Length())) { + continue; } } - PRInt32 advance = (PRInt32)(advances[k] * appUnitsPerDevPixel); - if (glyphCount == 1 && advance >= 0 && - glyphOffsets[k].advanceOffset == 0 && - glyphOffsets[k].ascenderOffset == 0 && - aTextRun->IsClusterStart(absC) && - gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && - gfxTextRun::CompressedGlyph::IsSimpleGlyphID(indices[k])) { - aTextRun->SetSimpleGlyph(absC, - g.SetSimpleGlyph(advance, - indices[k])); - } else { - if (detailedGlyphs.Length() < glyphCount) { - if (!detailedGlyphs.AppendElements( - glyphCount - detailedGlyphs.Length())) { - continue; - } + float totalAdvance = 0; + for (unsigned int z = 0; z < glyphCount; z++) { + detailedGlyphs[z].mGlyphID = indices[k + z]; + detailedGlyphs[z].mAdvance = + (PRInt32)(advances[k + z] + * appUnitsPerDevPixel); + if (readingDirection == + DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) { + detailedGlyphs[z].mXOffset = + (totalAdvance + + glyphOffsets[k + z].advanceOffset) + * appUnitsPerDevPixel; + } else { + detailedGlyphs[z].mXOffset = + glyphOffsets[k + z].advanceOffset * + appUnitsPerDevPixel; } - float totalAdvance = 0; - for (unsigned int z = 0; z < glyphCount; z++) { - detailedGlyphs[z].mGlyphID = indices[k + z]; - detailedGlyphs[z].mAdvance = - (PRInt32)(advances[k + z] - * appUnitsPerDevPixel); - if (readingDirection == - DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) { - detailedGlyphs[z].mXOffset = - (totalAdvance + - glyphOffsets[k + z].advanceOffset) - * appUnitsPerDevPixel; - } else { - detailedGlyphs[z].mXOffset = - glyphOffsets[k + z].advanceOffset * - appUnitsPerDevPixel; - } - detailedGlyphs[z].mYOffset = - -glyphOffsets[k + z].ascenderOffset * - appUnitsPerDevPixel; - totalAdvance += advances[k + z]; - } - aTextRun->SetGlyphs( - absC, - g.SetComplex(aTextRun->IsClusterStart(absC), - true, - glyphCount), - detailedGlyphs.Elements()); + detailedGlyphs[z].mYOffset = + -glyphOffsets[k + z].ascenderOffset * + appUnitsPerDevPixel; + totalAdvance += advances[k + z]; } + aShapedWord->SetGlyphs( + absC, + g.SetComplex(aShapedWord->IsClusterStart(absC), + true, + glyphCount), + detailedGlyphs.Elements()); } } - return result; + return true; }
--- a/gfx/thebes/gfxDWriteShaper.h +++ b/gfx/thebes/gfxDWriteShaper.h @@ -52,17 +52,14 @@ public: MOZ_COUNT_CTOR(gfxDWriteShaper); } virtual ~gfxDWriteShaper() { MOZ_COUNT_DTOR(gfxDWriteShaper); } - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString); }; #endif /* GFX_DWRITESHAPER_H */
--- a/gfx/thebes/gfxDWriteTextAnalysis.cpp +++ b/gfx/thebes/gfxDWriteTextAnalysis.cpp @@ -40,80 +40,54 @@ TextAnalysis::TextAnalysis(const wchar_t* text, UINT32 textLength, const wchar_t* localeName, DWRITE_READING_DIRECTION readingDirection) : mText(text) , mTextLength(textLength) , mLocaleName(localeName) , mReadingDirection(readingDirection) - , mBreakpoints(NULL) - , mRunHead(NULL) , mCurrentRun(NULL) { } TextAnalysis::~TextAnalysis() { - delete [] mBreakpoints; - for (Run *run = mRunHead; run;) { + // delete runs, except mRunHead which is part of the TextAnalysis object + for (Run *run = mRunHead.nextRun; run;) { Run *origRun = run; run = run->nextRun; delete origRun; } } STDMETHODIMP TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer, - OUT Run **runHead, - OUT DWRITE_LINE_BREAKPOINT **breakpoints) + OUT Run **runHead) { - // Analyzes the text using each of the analyzers and returns - // their results as a series of runs. + // Analyzes the text using the script analyzer and returns + // the result as a series of runs. HRESULT hr = S_OK; // Initially start out with one result that covers the entire range. // This result will be subdivided by the analysis processes. - mRunHead = new Run; - - mRunHead->mTextStart = 0; - mRunHead->mTextLength = mTextLength; - mRunHead->mBidiLevel = + mRunHead.mTextStart = 0; + mRunHead.mTextLength = mTextLength; + mRunHead.mBidiLevel = (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); - mRunHead->nextRun = NULL; - mCurrentRun = mRunHead; -#ifdef USE_DWRITE_BREAKPOINTS - delete [] mBreakpoints; - mBreakpoints = new DWRITE_LINE_BREAKPOINT[mTextLength]; -#endif + mRunHead.nextRun = NULL; + mCurrentRun = &mRunHead; // Call each of the analyzers in sequence, recording their results. - if ( -#ifdef USE_DWRITE_BREAKPOINTS - SUCCEEDED(hr = textAnalyzer->AnalyzeLineBreakpoints(this, - 0, - mTextLength, - this)) && -#endif - SUCCEEDED(hr = textAnalyzer->AnalyzeBidi(this, - 0, - mTextLength, - this)) && - SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this, + if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this, 0, mTextLength, - this)) && - SUCCEEDED(hr = textAnalyzer->AnalyzeNumberSubstitution(this, - 0, - mTextLength, - this))) { - *breakpoints = mBreakpoints; - - *runHead = mRunHead; + this))) { + *runHead = &mRunHead; } return hr; } //////////////////////////////////////////////////////////////////////////////// // IDWriteTextAnalysisSource source implementation @@ -190,21 +164,17 @@ TextAnalysis::GetNumberSubstitution(UINT //////////////////////////////////////////////////////////////////////////////// // IDWriteTextAnalysisSink implementation IFACEMETHODIMP TextAnalysis::SetLineBreakpoints(UINT32 textPosition, UINT32 textLength, DWRITE_LINE_BREAKPOINT const* lineBreakpoints) { - if (textLength > 0) { - memcpy(mBreakpoints + textPosition, - lineBreakpoints, - textLength * sizeof(DWRITE_LINE_BREAKPOINT)); - } + // We don't use this for now. return S_OK; } IFACEMETHODIMP TextAnalysis::SetScriptAnalysis(UINT32 textPosition, UINT32 textLength, DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) @@ -221,23 +191,17 @@ TextAnalysis::SetScriptAnalysis(UINT32 t IFACEMETHODIMP TextAnalysis::SetBidiLevel(UINT32 textPosition, UINT32 textLength, UINT8 explicitLevel, UINT8 resolvedLevel) { - SetCurrentRun(textPosition); - SplitCurrentRun(textPosition); - while (textLength > 0) { - Run *run = FetchNextRun(&textLength); - run->mBidiLevel = resolvedLevel; - } - + // We don't use this for now. return S_OK; } IFACEMETHODIMP TextAnalysis::SetNumberSubstitution(UINT32 textPosition, UINT32 textLength, IDWriteNumberSubstitution* numberSubstitution) @@ -279,17 +243,17 @@ void TextAnalysis::SetCurrentRun(UINT32 // Since the analyzers generally return results in a forward manner, // this will usually just return early. If not, find the // corresponding run for the text position. if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) { return; } - for (Run *run = mRunHead; run; run = run->nextRun) { + for (Run *run = &mRunHead; run; run = run->nextRun) { if (run->ContainsTextPosition(textPosition)) { mCurrentRun = run; return; } } NS_NOTREACHED("We should always be able to find the text position in one \ of our runs"); }
--- a/gfx/thebes/gfxDWriteTextAnalysis.h +++ b/gfx/thebes/gfxDWriteTextAnalysis.h @@ -101,18 +101,17 @@ public: TextAnalysis(const wchar_t* text, UINT32 textLength, const wchar_t* localeName, DWRITE_READING_DIRECTION readingDirection); ~TextAnalysis(); STDMETHODIMP GenerateResults(IDWriteTextAnalyzer* textAnalyzer, - Run **runHead, - DWRITE_LINE_BREAKPOINT **breakpoints); + Run **runHead); // IDWriteTextAnalysisSource implementation IFACEMETHODIMP GetTextAtPosition(UINT32 textPosition, OUT WCHAR const** textString, OUT UINT32* textLength); IFACEMETHODIMP GetTextBeforePosition(UINT32 textPosition, @@ -167,16 +166,13 @@ protected: UINT32 mTextLength; const wchar_t* mText; const wchar_t* mLocaleName; DWRITE_READING_DIRECTION mReadingDirection; // Current processing state. Run *mCurrentRun; - // Output - Run *mRunHead; - - // We do not use this for now, store anyway - DWRITE_LINE_BREAKPOINT *mBreakpoints; + // Output is a list of runs starting here + Run mRunHead; }; #endif /* GFX_DWRITETEXTANALYSIS_H */
--- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -420,79 +420,78 @@ gfxFT2FontGroup::WhichSystemFontSupports #endif // !ANDROID /** * gfxFT2Font */ bool -gfxFT2Font::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping) +gfxFT2Font::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString, + bool aPreferPlatformShaping) { bool ok = false; #ifdef MOZ_GRAPHITE if (FontCanSupportGraphite()) { if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { if (!mGraphiteShaper) { mGraphiteShaper = new gfxGraphiteShaper(this); } - ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString); } } #endif - if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) { + if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) { if (!mHarfBuzzShaper) { gfxFT2LockedFace face(this); mFUnitsConvFactor = face.XScale(); mHarfBuzzShaper = new gfxHarfBuzzShaper(this); } - ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, aRunScript); + ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString); } if (!ok) { - AddRange(aTextRun, aString, aRunStart, aRunLength); + AddRange(aShapedWord, aString); } - aTextRun->AdjustAdvancesForSyntheticBold(aContext, aRunStart, aRunLength); + if (IsSyntheticBold()) { + float synBoldOffset = + GetSyntheticBoldOffset() * CalcXScale(aContext); + aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset); + } return true; } void -gfxFT2Font::AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len) +gfxFT2Font::AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str) { - const PRUint32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + const PRUint32 appUnitsPerDevUnit = aShapedWord->AppUnitsPerDevUnit(); // we'll pass this in/figure it out dynamically, but at this point there can be only one face. gfxFT2LockedFace faceLock(this); FT_Face face = faceLock.get(); - gfxTextRun::CompressedGlyph g; + gfxShapedWord::CompressedGlyph g; const gfxFT2Font::CachedGlyphData *cgd = nsnull, *cgdNext = nsnull; FT_UInt spaceGlyph = GetSpaceGlyph(); + PRUint32 len = aShapedWord->Length(); for (PRUint32 i = 0; i < len; i++) { - PRUint32 ch = str[offset + i]; + PRUnichar ch = str[i]; if (ch == 0) { // treat this null byte as a missing glyph, don't create a glyph for it - aTextRun->SetMissingGlyph(offset + i, 0); + aShapedWord->SetMissingGlyph(i, 0, this); continue; } NS_ASSERTION(!gfxFontGroup::IsInvalidChar(ch), "Invalid char detected"); if (cgdNext) { cgd = cgdNext; cgdNext = nsnull; @@ -503,22 +502,22 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu FT_UInt gid = cgd->glyphIndex; PRInt32 advance = 0; if (gid == 0) { advance = -1; // trigger the missing glyphs case below } else { // find next character and its glyph -- in case they exist // and exist in the current font face -- to compute kerning - PRUint32 chNext = 0; + PRUnichar chNext = 0; FT_UInt gidNext = 0; FT_Pos lsbDeltaNext = 0; if (FT_HAS_KERNING(face) && i + 1 < len) { - chNext = str[offset + i + 1]; + chNext = str[i + 1]; if (chNext != 0) { cgdNext = GetGlyphDataForChar(chNext); gidNext = cgdNext->glyphIndex; if (gidNext && gidNext != spaceGlyph) lsbDeltaNext = cgdNext->lsbDelta; } } @@ -536,37 +535,33 @@ gfxFT2Font::AddRange(gfxTextRun *aTextRu } } // convert 26.6 fixed point to app units // round rather than truncate to nearest pixel // because these advances are often scaled advance = ((advance * appUnitsPerDevUnit + 32) >> 6); } -#ifdef DEBUG_thebes_2 - printf(" gid=%d, advance=%d (%s)\n", gid, advance, - NS_LossyConvertUTF16toASCII(font->GetName()).get()); -#endif if (advance >= 0 && - gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && - gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gid)) { - aTextRun->SetSimpleGlyph(offset + i, g.SetSimpleGlyph(advance, gid)); + gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advance) && + gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gid)) { + aShapedWord->SetSimpleGlyph(i, g.SetSimpleGlyph(advance, gid)); } else if (gid == 0) { // gid = 0 only happens when the glyph is missing from the font - aTextRun->SetMissingGlyph(offset + i, ch); + aShapedWord->SetMissingGlyph(i, ch, this); } else { gfxTextRun::DetailedGlyph details; details.mGlyphID = gid; NS_ASSERTION(details.mGlyphID == gid, "Seriously weird glyph ID detected!"); details.mAdvance = advance; details.mXOffset = 0; details.mYOffset = 0; - g.SetComplex(aTextRun->IsClusterStart(offset + i), true, 1); - aTextRun->SetGlyphs(offset + i, g, &details); + g.SetComplex(aShapedWord->IsClusterStart(i), true, 1); + aShapedWord->SetGlyphs(i, g, &details); } } } gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont, FT2FontEntry *aFontEntry, const gfxFontStyle *aFontStyle, bool aNeedsBold)
--- a/gfx/thebes/gfxFT2Fonts.h +++ b/gfx/thebes/gfxFT2Fonts.h @@ -92,27 +92,24 @@ public: // new functions // this is a new entry, fill it FillGlyphDataForChar(ch, &entry->mData); } return &entry->mData; } protected: - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping = false); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString, + bool aPreferPlatformShaping = false); void FillGlyphDataForChar(PRUint32 ch, CachedGlyphData *gd); - void AddRange(gfxTextRun *aTextRun, const PRUnichar *str, PRUint32 offset, PRUint32 len); + void AddRange(gfxShapedWord *aShapedWord, const PRUnichar *str); typedef nsBaseHashtableET<nsUint32HashKey, CachedGlyphData> CharGlyphMapEntryType; typedef nsTHashtable<CharGlyphMapEntryType> CharGlyphMap; CharGlyphMap mCharGlyphCache; }; #ifndef ANDROID // not needed on Android, uses the standard gfxFontGroup directly class THEBES_API gfxFT2FontGroup : public gfxFontGroup {
--- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -43,16 +43,17 @@ #endif #include "prlog.h" #include "nsServiceManagerUtils.h" #include "nsReadableUtils.h" #include "nsExpirationTracker.h" #include "nsILanguageAtomService.h" #include "nsIMemoryReporter.h" +#include "nsITimer.h" #include "gfxFont.h" #include "gfxPlatform.h" #include "gfxAtoms.h" #include "prtypes.h" #include "gfxTypes.h" #include "nsAlgorithm.h" @@ -976,16 +977,46 @@ gfxFontCache::Shutdown() printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize); printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple); printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight); printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight); printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight); #endif } +gfxFontCache::gfxFontCache() + : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000) +{ + mFonts.Init(); + mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mWordCacheExpirationTimer) { + mWordCacheExpirationTimer-> + InitWithFuncCallback(WordCacheExpirationTimerCallback, this, + SHAPED_WORD_TIMEOUT_SECONDS * 1000, + nsITimer::TYPE_REPEATING_SLACK); + } +} + +gfxFontCache::~gfxFontCache() +{ + if (mWordCacheExpirationTimer) { + mWordCacheExpirationTimer->Cancel(); + mWordCacheExpirationTimer = nsnull; + } + + // Expire everything that has a zero refcount, so we don't leak them. + AgeAllGenerations(); + // All fonts should be gone. + NS_WARN_IF_FALSE(mFonts.Count() == 0, + "Fonts still alive while shutting down gfxFontCache"); + // Note that we have to delete everything through the expiration + // tracker, since there might be fonts not in the hashtable but in + // the tracker. +} + bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const { return aKey->mFontEntry == mFont->GetFontEntry() && aKey->mStyle->Equals(*mFont->GetStyle()); } already_AddRefed<gfxFont> @@ -1050,16 +1081,32 @@ gfxFontCache::DestroyFont(gfxFont *aFont HashEntry *entry = mFonts.GetEntry(key); if (entry && entry->mFont == aFont) mFonts.RemoveEntry(key); NS_ASSERTION(aFont->GetRefCount() == 0, "Destroying with non-zero ref count!"); delete aFont; } +/*static*/ +PLDHashOperator +gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) +{ + aHashEntry->mFont->AgeCachedWords(); + return PL_DHASH_NEXT; +} + +/*static*/ +void +gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache) +{ + gfxFontCache* cache = static_cast<gfxFontCache*>(aCache); + cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nsnull); +} + void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) { mAscent = NS_MAX(mAscent, aOther.mAscent); mDescent = NS_MAX(mDescent, aOther.mDescent); if (aOtherIsOnLeft) { mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox); @@ -1091,16 +1138,30 @@ gfxFont::~gfxFont() // We destroy the contents of mGlyphExtentsArray explicitly instead of // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs // of classes that lack a proper copy constructor for (i = 0; i < mGlyphExtentsArray.Length(); ++i) { delete mGlyphExtentsArray[i]; } } +/*static*/ +PLDHashOperator +gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData) +{ + if (!aEntry->mShapedWord) { + NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!"); + return PL_DHASH_REMOVE; + } + if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) { + return PL_DHASH_REMOVE; + } + return PL_DHASH_NEXT; +} + hb_blob_t * gfxFont::GetFontTable(PRUint32 aTag) { hb_blob_t *blob; if (mFontEntry->GetExistingFontTable(aTag, &blob)) return blob; FallibleTArray<PRUint8> buffer; bool haveTable = NS_SUCCEEDED(mFontEntry->GetFontTable(aTag, buffer)); @@ -1192,18 +1253,18 @@ struct GlyphBufferAzure { }; // Bug 674909. When synthetic bolding text by drawing twice, need to // render using a pixel offset in device pixels, otherwise text // doesn't appear bolded, it appears as if a bad text shadow exists // when a non-identity transform exists. Use an offset factor so that // the second draw occurs at a constant offset in device pixels. -static double -CalcXScale(gfxContext *aContext) +double +gfxFont::CalcXScale(gfxContext *aContext) { // determine magnitude of a 1px x offset in device space gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0)); if (t.width == 1.0 && t.height == 0.0) { // short-circuit the most common case to avoid sqrt() and division return 1.0; } @@ -1723,144 +1784,325 @@ gfxFont::Measure(gfxTextRun *aTextRun, #define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid // over-stressing platform shapers #define BACKTRACK_LIMIT 1024 // If we can't find a space or a cluster start // within 1K chars, just chop arbitrarily. // Limiting backtrack here avoids pathological // behavior on long runs with no whitespace. -bool -gfxFont::SplitAndInitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +static bool +IsClusterExtender(PRUint32 aUSV) +{ + PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aUSV); + return ((category >= HB_CATEGORY_COMBINING_MARK && + category <= HB_CATEGORY_NON_SPACING_MARK) || + (aUSV >= 0x200c && aUSV <= 0x200d) || // ZWJ, ZWNJ + (aUSV >= 0xff9e && aUSV <= 0xff9f)); // katakana sound marks +} + +static bool +IsBoundarySpace(PRUnichar aChar, PRUnichar aNextChar) +{ + return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar); +} + +static inline PRUint32 +HashMix(PRUint32 aHash, PRUnichar aCh) { + return (aHash >> 28) ^ (aHash << 4) ^ aCh; +} + +template<typename T> +gfxShapedWord* +gfxFont::GetShapedWord(gfxContext *aContext, + const T *aText, + PRUint32 aLength, + PRUint32 aHash, + PRInt32 aRunScript, + PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) +{ + // if there's a cached entry for this word, just return it + CacheHashKey key(aText, aLength, aHash, + aRunScript, + aAppUnitsPerDevUnit, + aFlags); + + CacheHashEntry *entry = mWordCache.PutEntry(key); + gfxShapedWord *sw = entry->mShapedWord; + if (sw) { + sw->ResetAge(); + return sw; + } + + sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength, + aRunScript, + aAppUnitsPerDevUnit, + aFlags); + NS_ASSERTION(sw != nsnull, + "failed to create gfxShapedWord - expect missing text"); + if (!sw) { + return nsnull; + } + bool ok; - -#ifdef PR_LOGGING - PRLogModuleInfo *log = (mStyle.systemFont ? - gfxPlatform::GetLog(eGfxLog_textrunui) : - gfxPlatform::GetLog(eGfxLog_textrun)); - - if (NS_UNLIKELY(log)) { - nsCAutoString lang; - mStyle.language->ToUTF8String(lang); - PR_LOG(log, PR_LOG_DEBUG,\ - ("(%s-fontmatching) font: [%s] lang: %s script: %d len: %d " - "TEXTRUN [%s] ENDTEXTRUN\n", - (mStyle.systemFont ? "textrunui" : "textrun"), - NS_ConvertUTF16toUTF8(GetName()).get(), - lang.get(), aRunScript, aRunLength, - NS_ConvertUTF16toUTF8(aString + aRunStart, aRunLength).get())); - } -#endif - - do { - // Because various shaping backends struggle with very long runs, - // we look for appropriate break locations (preferring whitespace), - // and shape sub-runs of no more than 32K characters at a time. - // See bug 606714 (CoreText), and similar Uniscribe issues. - // This loop always executes at least once, and "processes" up to - // MAX_RUN_LENGTH_FOR_SHAPING characters, updating aRunStart and - // aRunLength accordingly. It terminates when the entire run has - // been processed, or when shaping fails. - - PRUint32 thisRunLength; - ok = false; - - if (aRunLength <= MAX_SHAPING_LENGTH) { - thisRunLength = aRunLength; - } else { - // We're splitting this font run because it's very long - PRUint32 offset = aRunStart + MAX_SHAPING_LENGTH; - PRUint32 clusterStart = 0; - while (offset > aRunStart + MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) { - if (aTextRun->IsClusterStart(offset)) { - if (!clusterStart) { - clusterStart = offset; - } - if (aString[offset] == ' ' || aString[offset - 1] == ' ') { - break; - } - } - --offset; - } - - if (offset > MAX_SHAPING_LENGTH - BACKTRACK_LIMIT) { - // we found a space, so break the run there - thisRunLength = offset - aRunStart; - } else if (clusterStart != 0) { - // didn't find a space, but we found a cluster start - thisRunLength = clusterStart - aRunStart; - } else { - // otherwise we'll simply break at MAX_SHAPING_LENGTH chars, - // which may interfere with shaping behavior (but in practice - // only pathological cases will lack ANY whitespace or cluster - // boundaries, so we don't really care; it won't affect any - // "real" text) - thisRunLength = MAX_SHAPING_LENGTH; - } + if (sizeof(T) == sizeof(PRUnichar)) { + ok = ShapeWord(aContext, sw, (const PRUnichar*)aText); + } else { + nsAutoString utf16; + AppendASCIItoUTF16(nsDependentCSubstring((const char*)aText, aLength), + utf16); + ok = ShapeWord(aContext, sw, utf16.BeginReading()); + } + NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); + + for (PRUint32 i = 0; i < aLength; ++i) { + if (aText[i] == ' ') { + sw->SetIsSpace(i); + } else if (i > 0 && + NS_IS_HIGH_SURROGATE(aText[i - 1]) && + NS_IS_LOW_SURROGATE(aText[i])) { + sw->SetIsLowSurrogate(i); } - - ok = InitTextRun(aContext, aTextRun, aString, - aRunStart, thisRunLength, aRunScript); - - aRunStart += thisRunLength; - aRunLength -= thisRunLength; - } while (ok && aRunLength > 0); - - NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text"); - return ok; + } + + return sw; } bool -gfxFont::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping) +gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const +{ + const gfxShapedWord *sw = mShapedWord; + if (!sw) { + return false; + } + if (sw->Length() != aKey->mLength || + sw->Flags() != aKey->mFlags || + sw->AppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit || + sw->Script() != aKey->mScript) { + return false; + } + if (sw->TextIs8Bit()) { + if (aKey->mTextIs8Bit) { + return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle, + aKey->mLength * sizeof(PRUint8))); + } + // The key has 16-bit text, even though all the characters are < 256, + // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're + // comparing with will have 8-bit text. + const PRUint8 *s1 = sw->Text8Bit(); + const PRUnichar *s2 = aKey->mText.mDouble; + const PRUnichar *s2end = s2 + aKey->mLength; + while (s2 < s2end) { + if (*s1++ != *s2++) { + return false; + } + } + return true; + } + NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 && + !aKey->mTextIs8Bit, "didn't expect 8-bit text here"); + return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble, + aKey->mLength * sizeof(PRUnichar))); +} + +bool +gfxFont::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText, + bool aPreferPlatformShaping) { bool ok = false; #ifdef MOZ_GRAPHITE if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aText); } #endif if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) { - if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) { - ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) { + ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aText); } } if (!ok) { if (!mPlatformShaper) { CreatePlatformShaper(); NS_ASSERTION(mPlatformShaper, "no platform shaper available!"); } if (mPlatformShaper) { - ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aText); } } + if (ok && IsSyntheticBold()) { + float synBoldOffset = + GetSyntheticBoldOffset() * CalcXScale(aContext); + aShapedWord->AdjustAdvancesForSyntheticBold(synBoldOffset); + } + return ok; } +template<typename T> +bool +gfxFont::SplitAndInitTextRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const T *aString, + PRUint32 aRunStart, + PRUint32 aRunLength, + PRInt32 aRunScript) +{ + if (aRunLength == 0) { + return true; + } + + InitWordCache(); + + // the only flags we care about for ShapedWord construction/caching + PRUint32 flags = aTextRun->GetFlags() & + (gfxTextRunFactory::TEXT_IS_RTL | + gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES); + if (sizeof(T) == sizeof(PRUint8)) { + flags |= gfxTextRunFactory::TEXT_IS_8BIT; + } + + const T *text = aString + aRunStart; + PRUint32 wordStart = 0; + PRUint32 hash = 0; + bool wordIs8Bit = true; + PRInt32 appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); + + T nextCh = text[0]; + for (PRUint32 i = 0; i <= aRunLength; ++i) { + T ch = nextCh; + nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n'; + bool boundary = IsBoundarySpace(ch, nextCh); + bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); + PRUint32 length = i - wordStart; + + // break into separate ShapedWords when we hit an invalid char, + // or a boundary space (always handled individually), + // or the first non-space after a space + bool breakHere = boundary || invalid; + + if (!breakHere) { + // if we're approaching the max ShapedWord length, break anyway... + if (sizeof(T) == sizeof(PRUint8)) { + // in 8-bit text, no clusters or surrogates to worry about + if (length >= gfxShapedWord::kMaxLength) { + breakHere = true; + } + } else { + // try to avoid breaking before combining mark or low surrogate + if (length >= gfxShapedWord::kMaxLength - 15) { + if (!NS_IS_LOW_SURROGATE(ch)) { + if (!IsClusterExtender(ch)) { + breakHere = true; + } + } + if (!breakHere && length >= gfxShapedWord::kMaxLength - 3) { + if (!NS_IS_LOW_SURROGATE(ch)) { + breakHere = true; + } + } + if (!breakHere && length >= gfxShapedWord::kMaxLength) { + breakHere = true; + } + } + } + } + + if (!breakHere) { + if (ch >= 0x100) { + wordIs8Bit = false; + } + // include this character in the hash, and move on to next + hash = HashMix(hash, ch); + continue; + } + + // We've decided to break here (i.e. we're at the end of a "word", + // or the word is becoming excessively long): shape the word and + // add it to the textrun + if (length > 0) { + PRUint32 wordFlags = flags; + // in the 8-bit version of this method, TEXT_IS_8BIT was + // already set as part of |flags|, so no need for a per-word + // adjustment here + if (sizeof(T) == sizeof(PRUnichar)) { + if (wordIs8Bit) { + wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT; + } + } + gfxShapedWord *sw = GetShapedWord(aContext, + text + wordStart, length, + hash, aRunScript, + appUnitsPerDevUnit, + wordFlags); + if (sw) { + aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart); + } else { + return false; // failed, presumably out of memory? + } + } + + if (boundary) { + // word was terminated by a space: add that to the textrun + if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext, + aRunStart + i, ch)) + { + static const PRUint8 space = ' '; + gfxShapedWord *sw = + GetShapedWord(aContext, + &space, 1, + HashMix(0, ' '), aRunScript, + appUnitsPerDevUnit, + flags | gfxTextRunFactory::TEXT_IS_8BIT); + if (sw) { + aTextRun->CopyGlyphDataFrom(sw, aRunStart + i); + } else { + return false; + } + } + hash = 0; + wordStart = i + 1; + wordIs8Bit = true; + continue; + } + + if (i == aRunLength) { + break; + } + + if (invalid) { + // word was terminated by an invalid char: skip it, + // but record where TAB or NEWLINE occur + if (ch == '\t') { + aTextRun->SetIsTab(aRunStart + i); + } else if (ch == '\n') { + aTextRun->SetIsNewline(aRunStart + i); + } + hash = 0; + wordStart = i + 1; + wordIs8Bit = true; + continue; + } + + // word was forcibly broken, so current char will begin next word + hash = HashMix(0, ch); + wordStart = i; + wordIs8Bit = (ch < 0x100); + } + + return true; +} + gfxGlyphExtents * gfxFont::GetOrCreateGlyphExtents(PRUint32 aAppUnitsPerDevUnit) { PRUint32 i; for (i = 0; i < mGlyphExtentsArray.Length(); ++i) { if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) return mGlyphExtentsArray[i]; } gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); @@ -2425,28 +2667,35 @@ gfxFontGroup::~gfxFontGroup() { gfxFontGroup * gfxFontGroup::Copy(const gfxFontStyle *aStyle) { return new gfxFontGroup(mFamilies, aStyle, mUserFontSet); } bool -gfxFontGroup::IsInvalidChar(PRUnichar ch) { - if (ch >= 32) { - return ch == 0x0085/*NEL*/ || - ((ch & 0xFF00) == 0x2000 /* Unicode control character */ && - (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ || - IS_BIDI_CONTROL_CHAR(ch))); - } - // We could just blacklist all control characters, but it seems better - // to only blacklist the ones we know cause problems for native font - // engines. - return ch == 0x0B || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' || - (ch >= 0x1c && ch <= 0x1f); +gfxFontGroup::IsInvalidChar(PRUint8 ch) +{ + return ((ch & 0x7f) < 0x20); +} + +bool +gfxFontGroup::IsInvalidChar(PRUnichar ch) +{ + // All printable 7-bit ASCII values are OK + if (ch >= ' ' && ch < 0x80) { + return false; + } + // No point in sending non-printing control chars through font shaping + if (ch <= 0x9f) { + return true; + } + return ((ch & 0xFF00) == 0x2000 /* Unicode control character */ && + (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/ || + IS_BIDI_CONTROL_CHAR(ch))); } bool gfxFontGroup::ForEachFont(FontCreationCallback fc, void *closure) { return ForEachFontInternal(mFamilies, mStyle.language, true, true, true, fc, closure); @@ -2648,214 +2897,308 @@ gfxFontGroup::MakeEmptyTextRun(const Par } gfxTextRun * gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags) { aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; static const PRUint8 space = ' '; - nsAutoPtr<gfxTextRun> textRun; - textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags); - if (!textRun) + gfxTextRun *textRun = gfxTextRun::Create(aParams, &space, 1, this, aFlags); + if (!textRun) { return nsnull; + } gfxFont *font = GetFontAt(0); if (NS_UNLIKELY(GetStyle()->size == 0)) { // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle // them, and always create at least size 1 fonts, i.e. they still // render something for size 0 fonts. textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false); } else { textRun->SetSpaceGlyph(font, aParams->mContext, 0); } + // Note that the gfxGlyphExtents glyph bounds storage for the font will // always contain an entry for the font's space glyph, so we don't have // to call FetchGlyphExtents here. - return textRun.forget(); -} - -#define UNICODE_LRO 0x202d -#define UNICODE_RLO 0x202e -#define UNICODE_PDF 0x202c - -inline void -AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString) -{ - static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO }; - aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]); - aString.Append(' '); + return textRun; } -inline void -AppendDirectionalIndicatorEnd(bool aNeedDirection, nsAString& aString) +gfxTextRun * +gfxFontGroup::MakeBlankTextRun(const void* aText, PRUint32 aLength, + const Parameters *aParams, PRUint32 aFlags) { - // append a space (always, for consistent treatment of last char, - // and a direction control if required (we skip this for 8-bit text, - // which is known to be unidirectional LTR, unless the direction was - // forced RTL via overrides) - aString.Append(' '); - if (!aNeedDirection) - return; - - aString.Append('.'); - aString.Append(UNICODE_PDF); + gfxTextRun *textRun = + gfxTextRun::Create(aParams, aText, aLength, this, aFlags); + if (!textRun) { + return nsnull; + } + + textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false); + return textRun; } gfxTextRun * gfxFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { - NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text"); - NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit"); - gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags); - if (!textRun) + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags); + } + + aFlags |= TEXT_IS_8BIT; + + if (GetStyle()->size == 0) { + // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle + // them, and always create at least size 1 fonts, i.e. they still + // render something for size 0 fonts. + return MakeBlankTextRun(aString, aLength, aParams, aFlags); + } + + gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, + this, aFlags); + if (!textRun) { return nsnull; - - nsDependentCSubstring cString(reinterpret_cast<const char*>(aString), - reinterpret_cast<const char*>(aString) + aLength); - - nsAutoString utf16; - AppendASCIItoUTF16(cString, utf16); - - InitTextRun(aParams->mContext, textRun, utf16.get(), utf16.Length()); + } + + InitTextRun(aParams->mContext, textRun, aString, aLength); textRun->FetchGlyphExtents(aParams->mContext); return textRun; } gfxTextRun * gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { - NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text"); - gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags); - if (!textRun) + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags); + } + if (GetStyle()->size == 0) { + return MakeBlankTextRun(aString, aLength, aParams, aFlags); + } + + gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, + this, aFlags); + if (!textRun) { return nsnull; - - gfxPlatform::GetPlatform()->SetupClusterBoundaries(textRun, aString); + } InitTextRun(aParams->mContext, textRun, aString, aLength); textRun->FetchGlyphExtents(aParams->mContext); return textRun; } +template<typename T> void gfxFontGroup::InitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, - const PRUnichar *aString, + const T *aString, PRUint32 aLength) { - // split into script runs so that script can potentially influence - // the font matching process below - gfxScriptItemizer scriptRuns(aString, aLength); - -#ifdef PR_LOGGING - PRLogModuleInfo *log = (mStyle.systemFont ? - gfxPlatform::GetLog(eGfxLog_textrunui) : - gfxPlatform::GetLog(eGfxLog_textrun)); -#endif - - PRUint32 runStart = 0, runLimit = aLength; - PRInt32 runScript = HB_SCRIPT_LATIN; - while (scriptRuns.Next(runStart, runLimit, runScript)) { + // we need to do numeral processing even on 8-bit text, + // in case we're converting Western to Hindi/Arabic digits + PRInt32 numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); + nsAutoArrayPtr<PRUnichar> transformedString; + if (numOption != IBMBIDI_NUMERAL_NOMINAL) { + // scan the string for numerals that may need to be transformed; + // if we find any, we'll make a local copy here and use that for + // font matching and glyph generation/shaping + bool prevIsArabic = + (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0; + for (PRUint32 i = 0; i < aLength; ++i) { + PRUnichar origCh = aString[i]; + PRUnichar newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); + if (newCh != origCh) { + if (!transformedString) { + transformedString = new PRUnichar[aLength]; + if (sizeof(T) == sizeof(PRUnichar)) { + memcpy(transformedString.get(), aString, i * sizeof(PRUnichar)); + } else { + for (PRUint32 j = 0; j < i; ++j) { + transformedString[j] = aString[j]; + } + } + } + } + if (transformedString) { + transformedString[i] = newCh; + } + prevIsArabic = IS_ARABIC_CHAR(newCh); + } + } + + if (sizeof(T) == sizeof(PRUint8) && !transformedString) { + // the text is still purely 8-bit; bypass the script-run itemizer + // and treat it as a single Latin run + InitScriptRun(aContext, aTextRun, aString, + 0, aLength, HB_SCRIPT_LATIN); + } else { + const PRUnichar *textPtr; + if (transformedString) { + textPtr = transformedString.get(); + } else { + // typecast to avoid compilation error for the 8-bit version, + // even though this is dead code in that case + textPtr = reinterpret_cast<const PRUnichar*>(aString); + } + + // split into script runs so that script can potentially influence + // the font matching process below + gfxScriptItemizer scriptRuns(textPtr, aLength); #ifdef PR_LOGGING - if (NS_UNLIKELY(log)) { - nsCAutoString lang; - mStyle.language->ToUTF8String(lang); - PRUint32 runLen = runLimit - runStart; - PR_LOG(log, PR_LOG_DEBUG,\ - ("(%s) fontgroup: [%s] lang: %s script: %d len %d " - "weight: %d width: %d style: %s " - "TEXTRUN [%s] ENDTEXTRUN\n", - (mStyle.systemFont ? "textrunui" : "textrun"), - NS_ConvertUTF16toUTF8(mFamilies).get(), - lang.get(), runScript, runLen, - PRUint32(mStyle.weight), PRUint32(mStyle.stretch), - (mStyle.style & FONT_STYLE_ITALIC ? "italic" : - (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" : - "normal")), - NS_ConvertUTF16toUTF8(aString + runStart, runLen).get())); + PRLogModuleInfo *log = (mStyle.systemFont ? + gfxPlatform::GetLog(eGfxLog_textrunui) : + gfxPlatform::GetLog(eGfxLog_textrun)); +#endif + + PRUint32 runStart = 0, runLimit = aLength; + PRInt32 runScript = HB_SCRIPT_LATIN; + while (scriptRuns.Next(runStart, runLimit, runScript)) { + +#ifdef PR_LOGGING + if (NS_UNLIKELY(log)) { + nsCAutoString lang; + mStyle.language->ToUTF8String(lang); + PRUint32 runLen = runLimit - runStart; + PR_LOG(log, PR_LOG_DEBUG,\ + ("(%s) fontgroup: [%s] lang: %s script: %d len %d " + "weight: %d width: %d style: %s " + "TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + NS_ConvertUTF16toUTF8(mFamilies).get(), + lang.get(), runScript, runLen, + PRUint32(mStyle.weight), PRUint32(mStyle.stretch), + (mStyle.style & FONT_STYLE_ITALIC ? "italic" : + (mStyle.style & FONT_STYLE_OBLIQUE ? "oblique" : + "normal")), + NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); + } +#endif + + InitScriptRun(aContext, aTextRun, textPtr, + runStart, runLimit, runScript); } -#endif - - InitScriptRun(aContext, aTextRun, aString, aLength, - runStart, runLimit, runScript); + } + + if (sizeof(T) == sizeof(PRUnichar) && aLength > 0) { + gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs(); + if (!glyph->IsSimpleGlyph()) { + glyph->SetClusterStart(true); + } } // It's possible for CoreText to omit glyph runs if it decides they contain // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we // need to eliminate them from the glyph run array to avoid drawing "partial // ligatures" with the wrong font. // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because // it will iterate back over all glyphruns in the textrun, which leads to // pathologically-bad perf in the case where a textrun contains many script // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs // every time a new script subrun is processed. aTextRun->SanitizeGlyphRuns(); aTextRun->SortGlyphRuns(); } +template<typename T> void gfxFontGroup::InitScriptRun(gfxContext *aContext, gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aTotalLength, + const T *aString, PRUint32 aScriptRunStart, PRUint32 aScriptRunEnd, PRInt32 aRunScript) { - gfxFont *mainFont = mFonts[0].get(); + gfxFont *mainFont = GetFontAt(0); PRUint32 runStart = aScriptRunStart; nsAutoTArray<gfxTextRange,3> fontRanges; - ComputeRanges(fontRanges, aString, - aScriptRunStart, aScriptRunEnd, aRunScript); + ComputeRanges(fontRanges, aString + aScriptRunStart, + aScriptRunEnd - aScriptRunStart, aRunScript); PRUint32 numRanges = fontRanges.Length(); for (PRUint32 r = 0; r < numRanges; r++) { const gfxTextRange& range = fontRanges[r]; PRUint32 matchedLength = range.Length(); gfxFont *matchedFont = (range.font ? range.font.get() : nsnull); // create the glyph run for this range if (matchedFont) { aTextRun->AddGlyphRun(matchedFont, range.matchType, runStart, (matchedLength > 0)); - } else { - aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, - runStart, (matchedLength > 0)); - } - if (matchedFont) { // do glyph layout and record the resulting positioned glyphs if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString, runStart, matchedLength, aRunScript)) { // glyph layout failed! treat as missing glyphs matchedFont = nsnull; } + } else { + aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, + runStart, (matchedLength > 0)); } + if (!matchedFont) { - for (PRUint32 index = runStart; index < runStart + matchedLength; index++) { - // Record the char code so we can draw a box with the Unicode value - PRUint32 ch = aString[index]; - if (NS_IS_HIGH_SURROGATE(ch) && - index + 1 < aScriptRunEnd && - NS_IS_LOW_SURROGATE(aString[index+1])) { - aTextRun->SetMissingGlyph(index, - SURROGATE_TO_UCS4(aString[index], - aString[index+1])); - index++; - } else { + // for PRUnichar text, we need to set cluster boundaries so that + // surrogate pairs, combining characters, etc behave properly, + // even if we don't have glyphs for them + if (sizeof(T) == sizeof(PRUnichar)) { + gfxShapedWord::SetupClusterBoundaries(aTextRun->GetCharacterGlyphs() + runStart, + reinterpret_cast<const PRUnichar*>(aString) + runStart, + matchedLength); + } + + // various "missing" characters may need special handling, + // so we check for them here + PRUint32 runLimit = runStart + matchedLength; + for (PRUint32 index = runStart; index < runLimit; index++) { + T ch = aString[index]; + + // tab and newline are not to be displayed as hexboxes, + // but do need to be recorded in the textrun + if (ch == '\n') { + aTextRun->SetIsNewline(index); + continue; + } + if (ch == '\t') { + aTextRun->SetIsTab(index); + continue; + } + + // for 16-bit textruns only, check for surrogate pairs and + // special Unicode spaces; omit these checks in 8-bit runs + if (sizeof(T) == sizeof(PRUnichar)) { + if (NS_IS_HIGH_SURROGATE(ch) && + index + 1 < aScriptRunEnd && + NS_IS_LOW_SURROGATE(aString[index + 1])) + { + aTextRun->SetMissingGlyph(index, + SURROGATE_TO_UCS4(ch, + aString[index + 1])); + index++; + aTextRun->SetIsLowSurrogate(index); + continue; + } + + // check if this is a known Unicode whitespace character that + // we can render using the space glyph with a custom width gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); if (wid >= 0.0) { nscoord advance = aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); gfxTextRun::CompressedGlyph g; if (gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance)) { aTextRun->SetSimpleGlyph(index, g.SetSimpleGlyph(advance, @@ -2864,28 +3207,34 @@ gfxFontGroup::InitScriptRun(gfxContext * gfxTextRun::DetailedGlyph detailedGlyph; detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); detailedGlyph.mAdvance = advance; detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; g.SetComplex(true, true, 1); aTextRun->SetGlyphs(index, g, &detailedGlyph); } - } else { - aTextRun->SetMissingGlyph(index, ch); + continue; } } + + if (IsInvalidChar(ch)) { + // invalid chars are left as zero-width/invisible + continue; + } + + // record char code so we can draw a box with the Unicode value + aTextRun->SetMissingGlyph(index, ch); } } runStart += matchedLength; } } - already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh, PRInt32 aRunScript, gfxFont *aPrevMatchedFont, PRUint8 *aMatchType) { nsRefPtr<gfxFont> selectedFont; // if this character is a join-control or the previous is a join-causer, @@ -2950,44 +3299,49 @@ gfxFontGroup::FindFontForChar(PRUint32 a *aMatchType = gfxTextRange::kSystemFallback; selectedFont = WhichSystemFontSupportsChar(aCh); return selectedFont.forget(); } return nsnull; } - +template<typename T> void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges, - const PRUnichar *aString, - PRUint32 begin, PRUint32 end, + const T *aString, PRUint32 aLength, PRInt32 aRunScript) { - const PRUnichar *str = aString + begin; - PRUint32 len = end - begin; - aRanges.Clear(); - if (len == 0) { + if (aLength == 0) { return; } PRUint32 prevCh = 0; gfxFont *prevFont = nsnull; PRUint8 matchType = 0; - for (PRUint32 i = 0; i < len; i++) { + for (PRUint32 i = 0; i < aLength; i++) { const PRUint32 origI = i; // save off in case we increase for surrogate // set up current ch - PRUint32 ch = str[i]; - if ((i+1 < len) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(str[i+1])) { - i++; - ch = SURROGATE_TO_UCS4(ch, str[i]); + PRUint32 ch = aString[i]; + + // in 16-bit case only, check for surrogate pair + if (sizeof(T) == sizeof(PRUnichar)) { + if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) && + NS_IS_LOW_SURROGATE(aString[i + 1])) { + i++; + ch = SURROGATE_TO_UCS4(ch, aString[i]); + } + } + + if (ch == 0xa0) { + ch = ' '; } // find the font for this char nsRefPtr<gfxFont> font = FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); prevCh = ch; @@ -2996,28 +3350,31 @@ void gfxFontGroup::ComputeRanges(nsTArra aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); prevFont = font; } else { // if font has changed, make a new range gfxTextRange& prevRange = aRanges[aRanges.Length() - 1]; if (prevRange.font != font || prevRange.matchType != matchType) { // close out the previous range prevRange.end = origI; - aRanges.AppendElement(gfxTextRange(origI, i+1, font, matchType)); + aRanges.AppendElement(gfxTextRange(origI, i + 1, + font, matchType)); // update prevFont for the next match, *unless* we switched // fonts on a ZWJ, in which case propagating the changed font // is probably not a good idea (see bug 619511) - if (!gfxFontUtils::IsJoinCauser(ch)) { + if (sizeof(T) == sizeof(PRUint8) || + !gfxFontUtils::IsJoinCauser(ch)) + { prevFont = font; } } } } - aRanges[aRanges.Length()-1].end = len; + aRanges[aRanges.Length() - 1].end = aLength; } gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; } @@ -3301,16 +3658,240 @@ gfxFontStyle::ComputeWeight() const if (baseWeight < 0) baseWeight = 0; if (baseWeight > 9) baseWeight = 9; return baseWeight; } +// This is not a member function of gfxShapedWord because it is also used +// by gfxFontGroup on missing-glyph runs, where we don't actually "shape" +// anything but still need to set cluster info. +/*static*/ void +gfxShapedWord::SetupClusterBoundaries(CompressedGlyph *aGlyphs, + const PRUnichar *aString, PRUint32 aLength) +{ + gfxTextRun::CompressedGlyph extendCluster; + extendCluster.SetComplex(false, true, 0); + + gfxUnicodeProperties::HSType hangulState = gfxUnicodeProperties::HST_NONE; + + for (PRUint32 i = 0; i < aLength; ++i) { + bool surrogatePair = false; + PRUint32 ch = aString[i]; + if (NS_IS_HIGH_SURROGATE(ch) && + i < aLength - 1 && NS_IS_LOW_SURROGATE(aString[i+1])) + { + ch = SURROGATE_TO_UCS4(ch, aString[i+1]); + surrogatePair = true; + } + + PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(ch); + gfxUnicodeProperties::HSType hangulType = gfxUnicodeProperties::HST_NONE; + + // combining marks extend the cluster + if (IsClusterExtender(ch)) { + aGlyphs[i] = extendCluster; + } else if (category == HB_CATEGORY_OTHER_LETTER) { + // handle special cases in Letter_Other category +#if 0 + // Currently disabled. This would follow the UAX#29 specification + // for extended grapheme clusters, but this is not favored by + // Thai users, at least for editing behavior. + // See discussion of equivalent Pango issue in bug 474068 and + // upstream at https://bugzilla.gnome.org/show_bug.cgi?id=576156. + + if ((ch & ~0xff) == 0x0e00) { + // specific Thai & Lao (U+0Exx) chars that extend the cluster + if ( ch == 0x0e30 || + (ch >= 0x0e32 && ch <= 0x0e33) || + ch == 0x0e45 || + ch == 0x0eb0 || + (ch >= 0x0eb2 && ch <= 0x0eb3)) + { + if (i > 0) { + aTextRun->SetGlyphs(i, extendCluster, nsnull); + } + } + else if ((ch >= 0x0e40 && ch <= 0x0e44) || + (ch >= 0x0ec0 && ch <= 0x0ec4)) + { + // characters that are prepended to the following cluster + if (i < length - 1) { + aTextRun->SetGlyphs(i+1, extendCluster, nsnull); + } + } + } else +#endif + if ((ch & ~0xff) == 0x1100 || + (ch >= 0xa960 && ch <= 0xa97f) || + (ch >= 0xac00 && ch <= 0xd7ff)) + { + // no break within Hangul syllables + hangulType = gfxUnicodeProperties::GetHangulSyllableType(ch); + switch (hangulType) { + case gfxUnicodeProperties::HST_L: + case gfxUnicodeProperties::HST_LV: + case gfxUnicodeProperties::HST_LVT: + if (hangulState == gfxUnicodeProperties::HST_L) { + aGlyphs[i] = extendCluster; + } + break; + case gfxUnicodeProperties::HST_V: + if ( (hangulState != gfxUnicodeProperties::HST_NONE) && + !(hangulState & gfxUnicodeProperties::HST_T)) + { + aGlyphs[i] = extendCluster; + } + break; + case gfxUnicodeProperties::HST_T: + if (hangulState & (gfxUnicodeProperties::HST_V | + gfxUnicodeProperties::HST_T)) + { + aGlyphs[i] = extendCluster; + } + break; + default: + break; + } + } + } + + if (surrogatePair) { + ++i; + aGlyphs[i] = extendCluster; + } + + hangulState = hangulType; + } +} + +gfxShapedWord::DetailedGlyph * +gfxShapedWord::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount) +{ + NS_ASSERTION(aIndex < Length(), "Index out of range"); + + if (!mDetailedGlyphs) { + mDetailedGlyphs = new DetailedGlyphStore(); + } + + DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); + if (!details) { + mCharacterGlyphs[aIndex].SetMissing(0); + return nsnull; + } + + return details; +} + +void +gfxShapedWord::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph, + const DetailedGlyph *aGlyphs) +{ + NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); + NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), + "First character can't be a ligature continuation!"); + + PRUint32 glyphCount = aGlyph.GetGlyphCount(); + if (glyphCount > 0) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); + if (!details) { + return; + } + memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); + } + mCharacterGlyphs[aIndex] = aGlyph; +} + +#include "ignorable.x-ccmap" +DEFINE_X_CCMAP(gIgnorableCCMapExt, const); + +static inline bool +IsDefaultIgnorable(PRUint32 aChar) +{ + return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar); +} + +void +gfxShapedWord::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont) +{ + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + if (!details) { + return; + } + + details->mGlyphID = aChar; + if (IsDefaultIgnorable(aChar)) { + // Setting advance width to zero will prevent drawing the hexbox + details->mAdvance = 0; + } else { + gfxFloat width = NS_MAX(aFont->GetMetrics().aveCharWidth, + gfxFontMissingGlyphs::GetDesiredMinWidth(aChar)); + details->mAdvance = PRUint32(width * mAppUnitsPerDevUnit); + } + details->mXOffset = 0; + details->mYOffset = 0; + mCharacterGlyphs[aIndex].SetMissing(1); +} + +bool +gfxShapedWord::FilterIfIgnorable(PRUint32 aIndex) +{ + PRUint32 ch = GetCharAt(aIndex); + if (IsDefaultIgnorable(ch)) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + if (details) { + details->mGlyphID = ch; + details->mAdvance = 0; + details->mXOffset = 0; + details->mYOffset = 0; + mCharacterGlyphs[aIndex].SetMissing(1); + return true; + } + } + return false; +} + +void +gfxShapedWord::AdjustAdvancesForSyntheticBold(float aSynBoldOffset) +{ + PRUint32 synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit; + for (PRUint32 i = 0; i < Length(); ++i) { + CompressedGlyph *glyphData = &mCharacterGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + // simple glyphs ==> just add the advance + PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; + if (CompressedGlyph::IsSimpleAdvance(advance)) { + glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); + } else { + // rare case, tested by making this the default + PRUint32 glyphIndex = glyphData->GetSimpleGlyph(); + glyphData->SetComplex(true, true, 1); + DetailedGlyph detail = {glyphIndex, advance, 0, 0}; + SetGlyphs(i, *glyphData, &detail); + } + } else { + // complex glyphs ==> add offset at cluster/ligature boundaries + PRUint32 detailedLength = glyphData->GetGlyphCount(); + if (detailedLength) { + DetailedGlyph *details = GetDetailedGlyphs(i); + if (!details) { + continue; + } + if (IsRightToLeft()) { + details[0].mAdvance += synAppUnitOffset; + } else { + details[detailedLength - 1].mAdvance += synAppUnitOffset; + } + } + } + } +} + bool gfxTextRun::GlyphRunIterator::NextRun() { if (mNextIndex >= mTextRun->mGlyphRuns.Length()) return false; mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; if (mGlyphRun->mCharacterOffset >= mEndOffset) return false; @@ -3330,169 +3911,112 @@ AccountStorageForTextRun(gfxTextRun *aTe // Ignores detailed glyphs... we don't know when those have been constructed // Also ignores gfxSkipChars dynamic storage (which won't be anything // for preformatted text) // Also ignores GlyphRun array, again because it hasn't been constructed // by the time this gets called. If there's only one glyphrun that's stored // directly in the textrun anyway so no additional overhead. PRUint32 length = aTextRun->GetLength(); PRInt32 bytes = length * sizeof(gfxTextRun::CompressedGlyph); - if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_PERSISTENT) { - bytes += length * ((aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) ? 1 : 2); - bytes += sizeof(gfxTextRun::CompressedGlyph) - 1; - bytes &= ~(sizeof(gfxTextRun::CompressedGlyph) - 1); - } bytes += sizeof(gfxTextRun); gTextRunStorage += bytes*aSign; gTextRunStorageHighWaterMark = NS_MAX(gTextRunStorageHighWaterMark, gTextRunStorage); } #endif -static PRUint64 -GlyphStorageAllocCount(PRUint32 aLength, PRUint32 aFlags) -{ - // always need to allocate storage for the glyph data - PRUint64 allocCount = aLength; - - // if the text is not persistent, we also need space for a copy - if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { - // figure out number of extra CompressedGlyph elements we need to - // get sufficient space for the text - typedef gfxTextRun::CompressedGlyph CompressedGlyph; - if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { - allocCount += (aLength + sizeof(CompressedGlyph) - 1) / - sizeof(CompressedGlyph); - } else { - allocCount += (aLength * sizeof(PRUnichar) + - sizeof(CompressedGlyph) - 1) / - sizeof(CompressedGlyph); - } - } - return allocCount; -} - -// Helper for textRun creation to preallocate storage for glyphs and text; -// this function returns a pointer to the newly-allocated glyph storage, -// AND modifies the aText parameter if TEXT_IS_PERSISTENT was not set. -// In that case, the text is appended to the glyph storage, so a single -// delete[] operation in the textRun destructor will free both. +// Helper for textRun creation to preallocate storage for glyph records; +// this function returns a pointer to the newly-allocated glyph storage. // Returns nsnull if allocation fails. -gfxTextRun::CompressedGlyph * -gfxTextRun::AllocateStorage(const void*& aText, PRUint32 aLength, PRUint32 aFlags) +void * +gfxTextRun::AllocateStorageForTextRun(size_t aSize, PRUint32 aLength) { - // Here, we rely on CompressedGlyph being the largest unit we care about for - // allocation/alignment of either glyph data or text, so we allocate an array - // of CompressedGlyphs, then take the last chunk of that and cast a pointer to - // PRUint8* or PRUnichar* for text storage. - - PRUint64 allocCount = GlyphStorageAllocCount(aLength, aFlags); - - // allocate the storage we need, returning nsnull on failure rather than - // throwing an exception (because web content can create huge runs) - CompressedGlyph *storage = new (std::nothrow) CompressedGlyph[allocCount]; + // Allocate the storage we need, returning nsnull on failure rather than + // throwing an exception (because web content can create huge runs). + void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph)); if (!storage) { - NS_WARNING("failed to allocate glyph/text storage for text run!"); + NS_WARNING("failed to allocate storage for text run!"); return nsnull; } - // copy the text if we need to keep a copy in the textrun - if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { - if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { - PRUint8 *newText = reinterpret_cast<PRUint8*>(storage + aLength); - memcpy(newText, aText, aLength); - aText = newText; - } else { - PRUnichar *newText = reinterpret_cast<PRUnichar*>(storage + aLength); - memcpy(newText, aText, aLength*sizeof(PRUnichar)); - aText = newText; - } - } + // Initialize the glyph storage (beyond aSize) to zero + memset(reinterpret_cast<char*>(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); return storage; } gfxTextRun * gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, const void *aText, PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags) { - CompressedGlyph *glyphStorage = AllocateStorage(aText, aLength, aFlags); - if (!glyphStorage) { + void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); + if (!storage) { return nsnull; } - return new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags, glyphStorage); + return new (storage) gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags); } gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText, - PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags, - CompressedGlyph *aGlyphStorage) - : mCharacterGlyphs(aGlyphStorage), - mUserData(aParams->mUserData), + PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags) + : mUserData(aParams->mUserData), mFontGroup(aFontGroup), mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit), - mFlags(aFlags), mCharacterCount(aLength), mHashCode(0) + mFlags(aFlags), mCharacterCount(aLength) { NS_ASSERTION(mAppUnitsPerDevUnit != 0, "Invalid app unit scale"); MOZ_COUNT_CTOR(gfxTextRun); NS_ADDREF(mFontGroup); + + mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); + if (aParams->mSkipChars) { mSkipChars.TakeFrom(aParams->mSkipChars); } - if (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) { - mText.mSingle = static_cast<const PRUint8 *>(aText); - } else { - mText.mDouble = static_cast<const PRUnichar *>(aText); - } #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS AccountStorageForTextRun(this, 1); #endif - mUserFontSetGeneration = mFontGroup->GetGeneration(); mSkipDrawing = mFontGroup->ShouldSkipDrawing(); } gfxTextRun::~gfxTextRun() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS AccountStorageForTextRun(this, -1); #endif #ifdef DEBUG // Make it easy to detect a dead text run mFlags = 0xFFFFFFFF; #endif - // this will also delete the text, if it is owned by the run, - // because we merge the storage allocations - delete [] mCharacterGlyphs; - NS_RELEASE(mFontGroup); MOZ_COUNT_DTOR(gfxTextRun); } bool gfxTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRUint8 *aBreakBefore, gfxContext *aRefContext) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Overflow"); - if (!mCharacterGlyphs) - return true; PRUint32 changed = 0; PRUint32 i; + CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart; for (i = 0; i < aLength; ++i) { PRUint8 canBreak = aBreakBefore[i]; - if (canBreak && !mCharacterGlyphs[aStart + i].IsClusterStart()) { + if (canBreak && !charGlyphs[i].IsClusterStart()) { // This can happen ... there is no guarantee that our linebreaking rules // align with the platform's idea of what constitutes a cluster. NS_WARNING("Break suggested inside cluster!"); canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; } - changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(canBreak); + changed |= charGlyphs[i].SetCanBreakBefore(canBreak); } return changed != 0; } gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(PRUint32 aPartStart, PRUint32 aPartEnd, PropertyProvider *aProvider) { @@ -3798,71 +4322,16 @@ struct BufferAlphaColor { mContext->Restore(); } gfxContext *mContext; gfxFloat mAlpha; }; void -gfxTextRun::AdjustAdvancesForSyntheticBold(gfxContext *aContext, - PRUint32 aStart, - PRUint32 aLength) -{ - const PRUint32 appUnitsPerDevUnit = GetAppUnitsPerDevUnit(); - bool isRTL = IsRightToLeft(); - - GlyphRunIterator iter(this, aStart, aLength); - while (iter.NextRun()) { - gfxFont *font = iter.GetGlyphRun()->mFont; - if (font->IsSyntheticBold()) { - PRUint32 synAppUnitOffset = - font->GetSyntheticBoldOffset() * - appUnitsPerDevUnit * CalcXScale(aContext); - PRUint32 start = iter.GetStringStart(); - PRUint32 end = iter.GetStringEnd(); - PRUint32 i; - - // iterate over glyphs, start to end - for (i = start; i < end; ++i) { - gfxTextRun::CompressedGlyph *glyphData = &mCharacterGlyphs[i]; - - if (glyphData->IsSimpleGlyph()) { - // simple glyphs ==> just add the advance - PRInt32 advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; - if (CompressedGlyph::IsSimpleAdvance(advance)) { - glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); - } else { - // rare case, tested by making this the default - PRUint32 glyphIndex = glyphData->GetSimpleGlyph(); - glyphData->SetComplex(true, true, 1); - DetailedGlyph detail = {glyphIndex, advance, 0, 0}; - SetGlyphs(i, *glyphData, &detail); - } - } else { - // complex glyphs ==> add offset at cluster/ligature boundaries - PRUint32 detailedLength = glyphData->GetGlyphCount(); - if (detailedLength) { - gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(i); - if (!details) { - continue; - } - if (isRTL) { - details[0].mAdvance += synAppUnitOffset; - } else { - details[detailedLength - 1].mAdvance += synAppUnitOffset; - } - } - } - } - } - } -} - -void gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider *aProvider, gfxFloat *aAdvanceWidth) { NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); gfxFloat direction = GetDirection(); @@ -4160,17 +4629,17 @@ gfxTextRun::BreakAndMeasureText(PRUint32 charAdvance += space->mBefore + space->mAfter; } } else { charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); } advance += charAdvance; if (aTrimWhitespace) { - if (GetChar(i) == ' ') { + if (mCharacterGlyphs[i].CharIsSpace()) { ++trimmableChars; trimmableAdvance += charAdvance; } else { trimmableAdvance = 0; trimmableChars = 0; } } } @@ -4284,16 +4753,20 @@ gfxTextRun::FindFirstGlyphRunContaining( "Hmm, something went wrong, aOffset should have been found"); return start; } nsresult gfxTextRun::AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType, PRUint32 aUTF16Offset, bool aForceNewRun) { + NS_ASSERTION(aFont, "adding glyph run for null font!"); + if (!aFont) { + return NS_OK; + } PRUint32 numGlyphRuns = mGlyphRuns.Length(); if (!aForceNewRun && numGlyphRuns > 0) { GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset, "Glyph runs out of order (and run not forced)"); // Don't append a run if the font is already the one we want @@ -4373,19 +4846,20 @@ gfxTextRun::SanitizeGlyphRuns() if (mGlyphRuns.Length() <= 1) return; // If any glyph run starts with ligature-continuation characters, we need to advance it // to the first "real" character to avoid drawing partial ligature glyphs from wrong font // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes // it appear as if a ligature has been formed) PRInt32 i, lastRunIndex = mGlyphRuns.Length() - 1; + const CompressedGlyph *charGlyphs = mCharacterGlyphs; for (i = lastRunIndex; i >= 0; --i) { GlyphRun& run = mGlyphRuns[i]; - while (mCharacterGlyphs[run.mCharacterOffset].IsLigatureContinuation() && + while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && run.mCharacterOffset < mCharacterCount) { run.mCharacterOffset++; } // if the run has become empty, eliminate it if ((i < lastRunIndex && run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || (i == lastRunIndex && run.mCharacterOffset == mCharacterCount)) { mGlyphRuns.RemoveElementAt(i); @@ -4407,20 +4881,16 @@ gfxTextRun::CountMissingGlyphs() return count; } gfxTextRun::DetailedGlyph * gfxTextRun::AllocateDetailedGlyphs(PRUint32 aIndex, PRUint32 aCount) { NS_ASSERTION(aIndex < mCharacterCount, "Index out of range"); - if (!mCharacterGlyphs) { - return nsnull; - } - if (!mDetailedGlyphs) { mDetailedGlyphs = new DetailedGlyphStore(); } DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); if (!details) { mCharacterGlyphs[aIndex].SetMissing(0); return nsnull; @@ -4429,42 +4899,39 @@ gfxTextRun::AllocateDetailedGlyphs(PRUin return details; } void gfxTextRun::SetGlyphs(PRUint32 aIndex, CompressedGlyph aGlyph, const DetailedGlyph *aGlyphs) { NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); - NS_ASSERTION(aIndex > 0 || - (aGlyph.IsClusterStart() && aGlyph.IsLigatureGroupStart()), - "First character must be the start of a cluster and can't be a ligature continuation!"); + NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), + "First character can't be a ligature continuation!"); PRUint32 glyphCount = aGlyph.GetGlyphCount(); if (glyphCount > 0) { DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); if (!details) return; memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); } mCharacterGlyphs[aIndex] = aGlyph; } -#include "ignorable.x-ccmap" -DEFINE_X_CCMAP(gIgnorableCCMapExt, const); - -static inline bool -IsDefaultIgnorable(PRUint32 aChar) -{ - return CCMAP_HAS_CHAR_EXT(gIgnorableCCMapExt, aChar); -} - void gfxTextRun::SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar) { + PRUint8 category = gfxUnicodeProperties::GetGeneralCategory(aChar); + if (category >= HB_CATEGORY_COMBINING_MARK && + category <= HB_CATEGORY_NON_SPACING_MARK) + { + mCharacterGlyphs[aIndex].SetComplex(false, true, 0); + } + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); if (!details) return; details->mGlyphID = aChar; GlyphRun *glyphRun = &mGlyphRuns[FindFirstGlyphRunContaining(aIndex)]; if (IsDefaultIgnorable(aChar)) { // Setting advance width to zero will prevent drawing the hexbox @@ -4474,68 +4941,78 @@ gfxTextRun::SetMissingGlyph(PRUint32 aIn gfxFontMissingGlyphs::GetDesiredMinWidth(aChar)); details->mAdvance = PRUint32(width*GetAppUnitsPerDevUnit()); } details->mXOffset = 0; details->mYOffset = 0; mCharacterGlyphs[aIndex].SetMissing(1); } -bool -gfxTextRun::FilterIfIgnorable(PRUint32 aIndex) +void +gfxTextRun::CopyGlyphDataFrom(const gfxShapedWord *aShapedWord, PRUint32 aOffset) { - PRUint32 ch = GetChar(aIndex); - if (IsDefaultIgnorable(ch)) { - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); - if (details) { - details->mGlyphID = ch; - details->mAdvance = 0; - details->mXOffset = 0; - details->mYOffset = 0; - mCharacterGlyphs[aIndex].SetMissing(1); - return true; + PRUint32 wordLen = aShapedWord->Length(); + NS_ASSERTION(aOffset + wordLen <= GetLength(), + "word overruns end of textrun!"); + + const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs(); + if (aShapedWord->HasDetailedGlyphs()) { + for (PRUint32 i = 0; i < wordLen; ++i, ++aOffset) { + const CompressedGlyph& g = wordGlyphs[i]; + if (g.IsSimpleGlyph()) { + SetSimpleGlyph(aOffset, g); + } else { + const DetailedGlyph *details = + g.GetGlyphCount() > 0 ? + aShapedWord->GetDetailedGlyphs(i) : nsnull; + SetGlyphs(aOffset, g, details); + } } - } - return false; + } else { + memcpy(GetCharacterGlyphs() + aOffset, wordGlyphs, + wordLen * sizeof(CompressedGlyph)); + } } void gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart, PRUint32 aLength, PRUint32 aDest) { NS_ASSERTION(aStart + aLength <= aSource->GetLength(), "Source substring out of range"); NS_ASSERTION(aDest + aLength <= GetLength(), "Destination substring out of range"); if (aSource->mSkipDrawing) { mSkipDrawing = true; } // Copy base glyph data, and DetailedGlyph data where present + const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart; + CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; for (PRUint32 i = 0; i < aLength; ++i) { - CompressedGlyph g = aSource->mCharacterGlyphs[i + aStart]; - g.SetCanBreakBefore(mCharacterGlyphs[i + aDest].CanBreakBefore()); + CompressedGlyph g = srcGlyphs[i]; + g.SetCanBreakBefore(dstGlyphs[i].CanBreakBefore()); if (!g.IsSimpleGlyph()) { PRUint32 count = g.GetGlyphCount(); if (count > 0) { DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); if (dst) { DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); if (src) { ::memcpy(dst, src, count * sizeof(DetailedGlyph)); } else { g.SetMissing(0); } } else { g.SetMissing(0); } } } - mCharacterGlyphs[i + aDest] = g; + dstGlyphs[i] = g; } // Copy glyph runs GlyphRunIterator iter(aSource, aStart, aLength); #ifdef DEBUG gfxFont *lastFont = nsnull; #endif while (iter.NextRun()) { @@ -4564,41 +5041,62 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, start - aStart + aDest, false); if (NS_FAILED(rv)) return; } } void -gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex) +gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, + PRUint32 aCharIndex) +{ + if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) { + return; + } + + aFont->InitWordCache(); + static const PRUint8 space = ' '; + gfxShapedWord *sw = aFont->GetShapedWord(aContext, + &space, 1, + HashMix(0, ' '), + HB_SCRIPT_LATIN, + mAppUnitsPerDevUnit, + gfxTextRunFactory::TEXT_IS_8BIT | + gfxTextRunFactory::TEXT_IS_ASCII | + gfxTextRunFactory::TEXT_IS_PERSISTENT); + if (sw) { + AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); + CopyGlyphDataFrom(sw, aCharIndex); + } +} + +bool +gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, + PRUint32 aCharIndex, PRUnichar aSpaceChar) { PRUint32 spaceGlyph = aFont->GetSpaceGlyph(); - float spaceWidth = aFont->GetMetrics().spaceWidth; - PRUint32 spaceWidthAppUnits = NS_lroundf(spaceWidth*mAppUnitsPerDevUnit); - if (!spaceGlyph || - !CompressedGlyph::IsSimpleGlyphID(spaceGlyph) || - !CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { - gfxTextRunFactory::Parameters params = { - aContext, nsnull, nsnull, nsnull, 0, mAppUnitsPerDevUnit - }; - static const PRUint8 space = ' '; - nsAutoPtr<gfxTextRun> textRun; - textRun = mFontGroup->MakeTextRun(&space, 1, ¶ms, - gfxTextRunFactory::TEXT_IS_8BIT | gfxTextRunFactory::TEXT_IS_ASCII | - gfxTextRunFactory::TEXT_IS_PERSISTENT); - if (!textRun || !textRun->mCharacterGlyphs) - return; - CopyGlyphDataFrom(textRun, 0, 1, aCharIndex); - return; - } + if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { + return false; + } + + PRUint32 spaceWidthAppUnits = + NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit); + if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { + return false; + } + AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); CompressedGlyph g; g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); + if (aSpaceChar == ' ') { + g.SetIsSpace(); + } SetSimpleGlyph(aCharIndex, g); + return true; } void gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) { bool needsGlyphExtents = NeedsGlyphExtents(this); if (!needsGlyphExtents && !mDetailedGlyphs) return; @@ -4711,56 +5209,44 @@ gfxTextRun::ClusterIterator::ClusterAdva return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); } size_t gfxTextRun::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) { // The second arg is how much gfxTextRun::AllocateStorage would have // allocated. - size_t total = - aMallocSizeOf(mCharacterGlyphs, - sizeof(CompressedGlyph) * - GlyphStorageAllocCount(mCharacterCount, mFlags)); + size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); if (mDetailedGlyphs) { total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); } - total += mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); - return total; } size_t gfxTextRun::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) { - return aMallocSizeOf(this, sizeof(gfxTextRun)) + + // The second arg is how much gfxTextRun::AllocateStorageForTextRun would + // have allocated, given the character count of this run. + return aMallocSizeOf(this, sizeof(gfxTextRun) + sizeof(CompressedGlyph) * GetLength()) + SizeOfExcludingThis(aMallocSizeOf); } #ifdef DEBUG void gfxTextRun::Dump(FILE* aOutput) { if (!aOutput) { aOutput = stdout; } PRUint32 i; - fputc('"', aOutput); - for (i = 0; i < mCharacterCount; ++i) { - PRUnichar ch = GetChar(i); - if (ch >= 32 && ch < 128) { - fputc(ch, aOutput); - } else { - fprintf(aOutput, "\\u%4x", ch); - } - } - fputs("\" [", aOutput); + fputc('[', aOutput); for (i = 0; i < mGlyphRuns.Length(); ++i) { if (i > 0) { fputc(',', aOutput); } gfxFont* font = mGlyphRuns[i].mFont; const gfxFontStyle* style = font->GetStyle(); NS_ConvertUTF16toUTF8 fontName(font->GetName()); nsCAutoString lang;
--- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -66,16 +66,17 @@ typedef struct _cairo_scaled_font cairo_ class gfxContext; class gfxTextRun; class gfxFont; class gfxFontFamily; class gfxFontGroup; class gfxUserFontSet; class gfxUserFontData; +class gfxShapedWord; class nsILanguageAtomService; typedef struct _hb_blob_t hb_blob_t; // We should eliminate these synonyms when it won't cause many merge conflicts. #define FONT_STYLE_NORMAL NS_FONT_STYLE_NORMAL #define FONT_STYLE_ITALIC NS_FONT_STYLE_ITALIC @@ -665,49 +666,54 @@ struct gfxTextRange { * When a font's refcount decreases to zero, instead of deleting it we * add it to our expiration tracker. * The expiration tracker tracks fonts with zero refcount. After a certain * period of time, such fonts expire and are deleted. * * We're using 3 generations with a ten-second generation interval, so * zero-refcount fonts will be deleted 20-30 seconds after their refcount * goes to zero, if timer events fire in a timely manner. + * + * The font cache also handles timed expiration of cached ShapedWords + * for "persistent" fonts: it has a repeating timer, and notifies + * each cached font to "age" its shaped words. The words will be released + * by the fonts if they get aged three times without being re-used in the + * meantime. + * + * Note that the ShapedWord timeout is much larger than the font timeout, + * so that in the case of a short-lived font, we'll discard the gfxFont + * completely, with all its words, and avoid the cost of aging the words + * individually. That only happens with longer-lived fonts. */ class THEBES_API gfxFontCache : public nsExpirationTracker<gfxFont,3> { public: - enum { TIMEOUT_SECONDS = 10 }; - gfxFontCache() - : nsExpirationTracker<gfxFont,3>(TIMEOUT_SECONDS*1000) { mFonts.Init(); } - ~gfxFontCache() { - // Expire everything that has a zero refcount, so we don't leak them. - AgeAllGenerations(); - // All fonts should be gone. - NS_WARN_IF_FALSE(mFonts.Count() == 0, - "Fonts still alive while shutting down gfxFontCache"); - // Note that we have to delete everything through the expiration - // tracker, since there might be fonts not in the hashtable but in - // the tracker. - } + enum { + FONT_TIMEOUT_SECONDS = 10, + SHAPED_WORD_TIMEOUT_SECONDS = 60 + }; + + gfxFontCache(); + ~gfxFontCache(); /* * Get the global gfxFontCache. You must call Init() before * calling this method --- the result will not be null. */ static gfxFontCache* GetCache() { return gGlobalCache; } static nsresult Init(); // It's OK to call this even if Init() has not been called. static void Shutdown(); // Look up a font in the cache. Returns an addrefed pointer, or null // if there's nothing matching in the cache already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry, - const gfxFontStyle *aFontGroup); + const gfxFontStyle *aStyle); // We created a new font (presumably because Lookup returned null); // put it in the cache. The font's refcount should be nonzero. It is // allowable to add a new font even if there is one already in the // cache with the same key; we'll forget about the old one. void AddNew(gfxFont *aFont); // The font's refcount has gone to zero; give ownership of it to // the cache. We delete it if it's not acquired again after a certain @@ -755,16 +761,129 @@ protected: return NS_PTR_TO_INT32(aKey->mFontEntry) ^ aKey->mStyle->Hash(); } enum { ALLOW_MEMMOVE = true }; gfxFont* mFont; }; nsTHashtable<HashEntry> mFonts; + + static PLDHashOperator AgeCachedWordsForFont(HashEntry* aHashEntry, void*); + static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache); + nsCOMPtr<nsITimer> mWordCacheExpirationTimer; +}; + +class THEBES_API gfxTextRunFactory { + NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory) + +public: + // Flags in the mask 0xFFFF0000 are reserved for textrun clients + // Flags in the mask 0x0000F000 are reserved for per-platform fonts + // Flags in the mask 0x00000FFF are set by the textrun creator. + enum { + CACHE_TEXT_FLAGS = 0xF0000000, + USER_TEXT_FLAGS = 0x0FFF0000, + PLATFORM_TEXT_FLAGS = 0x0000F000, + TEXTRUN_TEXT_FLAGS = 0x00000FFF, + SETTABLE_FLAGS = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS, + + /** + * When set, the text string pointer used to create the text run + * is guaranteed to be available during the lifetime of the text run. + */ + TEXT_IS_PERSISTENT = 0x0001, + /** + * When set, the text is known to be all-ASCII (< 128). + */ + TEXT_IS_ASCII = 0x0002, + /** + * When set, the text is RTL. + */ + TEXT_IS_RTL = 0x0004, + /** + * When set, spacing is enabled and the textrun needs to call GetSpacing + * on the spacing provider. + */ + TEXT_ENABLE_SPACING = 0x0008, + /** + * When set, GetHyphenationBreaks may return true for some character + * positions, otherwise it will always return false for all characters. + */ + TEXT_ENABLE_HYPHEN_BREAKS = 0x0010, + /** + * When set, the text has no characters above 255 and it is stored + * in the textrun in 8-bit format. + */ + TEXT_IS_8BIT = 0x0020, + /** + * When set, the RunMetrics::mBoundingBox field will be initialized + * properly based on glyph extents, in particular, glyph extents that + * overflow the standard font-box (the box defined by the ascent, descent + * and advance width of the glyph). When not set, it may just be the + * standard font-box even if glyphs overflow. + */ + TEXT_NEED_BOUNDING_BOX = 0x0040, + /** + * When set, optional ligatures are disabled. Ligatures that are + * required for legible text should still be enabled. + */ + TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080, + /** + * When set, the textrun should favour speed of construction over + * quality. This may involve disabling ligatures and/or kerning or + * other effects. + */ + TEXT_OPTIMIZE_SPEED = 0x0100, + /** + * For internal use by the memory reporter when accounting for + * storage used by textruns. + * Because the reporter may visit each textrun multiple times while + * walking the frame trees and textrun cache, it needs to mark + * textruns that have been seen so as to avoid multiple-accounting. + */ + TEXT_RUN_SIZE_ACCOUNTED = 0x0200, + + /** + * nsTextFrameThebes sets these, but they're defined here rather than + * in nsTextFrameUtils.h because ShapedWord creation/caching also needs + * to check the _INCOMING flag + */ + TEXT_TRAILING_ARABICCHAR = 0x20000000, + /** + * When set, the previous character for this textrun was an Arabic + * character. This is used for the context detection necessary for + * bidi.numeral implementation. + */ + TEXT_INCOMING_ARABICCHAR = 0x40000000, + + TEXT_UNUSED_FLAGS = 0x90000000 + }; + + /** + * This record contains all the parameters needed to initialize a textrun. + */ + struct Parameters { + // A reference context suggesting where the textrun will be rendered + gfxContext *mContext; + // Pointer to arbitrary user data (which should outlive the textrun) + void *mUserData; + // A description of which characters have been stripped from the original + // DOM string to produce the characters in the textrun. May be null + // if that information is not relevant. + gfxSkipChars *mSkipChars; + // A list of where linebreaks are currently placed in the textrun. May + // be null if mInitialBreakCount is zero. + PRUint32 *mInitialBreaks; + PRUint32 mInitialBreakCount; + // The ratio to use to convert device pixels to application layout units + PRUint32 mAppUnitsPerDevUnit; + }; + + virtual ~gfxTextRunFactory() {} }; /** * This stores glyph bounds information for a particular gfxFont, at * a particular appunits-per-dev-pixel ratio (because the compressed glyph * width array is stored in appunits). * * We store a hashtable from glyph IDs to float bounding rects. For the @@ -902,22 +1021,19 @@ public: gfxFontShaper(gfxFont *aFont) : mFont(aFont) { NS_ASSERTION(aFont, "shaper requires a valid font!"); } virtual ~gfxFontShaper() { } - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) = 0; + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText) = 0; gfxFont *GetFont() const { return mFont; } protected: // the font this shaper is working with gfxFont * mFont; }; @@ -1247,26 +1363,140 @@ public: return 0; } return mFontEntry->GetUVSGlyph(aCh, aVS); } // call the (virtual) InitTextRun method to do glyph generation/shaping, // limiting the length of text passed by processing the run in multiple // segments if necessary + template<typename T> bool SplitAndInitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript); + gfxTextRun *aTextRun, + const T *aString, + PRUint32 aRunStart, + PRUint32 aRunLength, + PRInt32 aRunScript); + + // Get a ShapedWord representing the given text (either 8- or 16-bit) + // for use in setting up a gfxTextRun. + template<typename T> + gfxShapedWord* GetShapedWord(gfxContext *aContext, + const T *aText, + PRUint32 aLength, + PRUint32 aHash, + PRInt32 aRunScript, + PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags); + + // Ensure the ShapedWord cache is initialized. This MUST be called before + // any attempt to use GetShapedWord(). + void InitWordCache() { + if (!mWordCache.IsInitialized()) { + mWordCache.Init(); + } + } + + // Called by the gfxFontCache timer to increment the age of all the words, + // so that they'll expire after a sufficient period of non-use + void AgeCachedWords() { + if (mWordCache.IsInitialized()) { + (void)mWordCache.EnumerateEntries(AgeCacheEntry, this); + } + } protected: + // Call the appropriate shaper to generate glyphs for aText and store + // them into aShapedWord. + // The length of the text is aShapedWord->Length(). + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText, + bool aPreferPlatformShaping = false); + nsRefPtr<gfxFontEntry> mFontEntry; + struct CacheHashKey { + union { + const PRUint8 *mSingle; + const PRUnichar *mDouble; + } mText; + PRUint32 mLength; + PRUint32 mFlags; + PRInt32 mScript; + PRInt32 mAppUnitsPerDevUnit; + PLDHashNumber mHashKey; + bool mTextIs8Bit; + + CacheHashKey(const PRUint8 *aText, PRUint32 aLength, + PRUint32 aStringHash, + PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) + : mLength(aLength), + mFlags(aFlags), + mScript(aScriptCode), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), + mHashKey(aStringHash + aScriptCode + + aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000), + mTextIs8Bit(true) + { + NS_ASSERTION(aFlags & gfxTextRunFactory::TEXT_IS_8BIT, + "8-bit flag should have been set"); + mText.mSingle = aText; + } + + CacheHashKey(const PRUnichar *aText, PRUint32 aLength, + PRUint32 aStringHash, + PRInt32 aScriptCode, PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) + : mLength(aLength), + mFlags(aFlags), + mScript(aScriptCode), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), + mHashKey(aStringHash + aScriptCode + + aAppUnitsPerDevUnit * 0x100 + aFlags * 0x10000), + mTextIs8Bit(false) + { + // We can NOT assert that TEXT_IS_8BIT is false in aFlags here, + // because this might be an 8bit-only word from a 16-bit textrun, + // in which case the text we're passed is still in 16-bit form, + // and we'll have to use an 8-to-16bit comparison in KeyEquals. + mText.mDouble = aText; + } + }; + + class CacheHashEntry : public PLDHashEntryHdr { + public: + typedef const CacheHashKey &KeyType; + typedef const CacheHashKey *KeyTypePointer; + + // When constructing a new entry in the hashtable, the caller of Put() + // will fill us in. + CacheHashEntry(KeyTypePointer aKey) { } + CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); } + ~CacheHashEntry() { } + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return aKey->mHashKey; + } + + enum { ALLOW_MEMMOVE = true }; + + nsAutoPtr<gfxShapedWord> mShapedWord; + }; + + nsTHashtable<CacheHashEntry> mWordCache; + + static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData); + static const PRUint32 kShapedWordCacheMaxAge = 3; + bool mIsValid; // use synthetic bolding for environments where this is not supported // by the platform bool mApplySyntheticBold; nsExpirationState mExpirationState; gfxFontStyle mStyle; @@ -1309,125 +1539,619 @@ protected: // Helper to calculate various derived metrics from the results of // InitMetricsFromSfntTables or equivalent platform code void CalculateDerivedMetrics(Metrics& aMetrics); // some fonts have bad metrics, this method sanitize them. // if this font has bad underline offset, aIsBadUnderlineFont should be true. void SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont); - // Default simply calls m[Platform|HarfBuzz]Shaper->InitTextRun(). - // Override if the font class wants to give special handling - // to shaper failure. - // Returns false if shaping failed (though currently we - // don't have any good way to handle that situation). - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping = false); + // Bug 674909. When synthetic bolding text by drawing twice, need to + // render using a pixel offset in device pixels, otherwise text + // doesn't appear bolded, it appears as if a bad text shadow exists + // when a non-identity transform exists. Use an offset factor so that + // the second draw occurs at a constant offset in device pixels. + // This helper calculates the scale factor we need to apply to the + // synthetic-bold offset. + static double CalcXScale(gfxContext *aContext); }; // proportion of ascent used for x-height, if unable to read value from font #define DEFAULT_XHEIGHT_FACTOR 0.56f -class THEBES_API gfxTextRunFactory { - NS_INLINE_DECL_REFCOUNTING(gfxTextRunFactory) - +/* + * gfxShapedWord stores a list of zero or more glyphs for each character. For each + * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset. + * The idea is that a string is rendered by a loop that draws each glyph + * at its designated offset from the current point, then advances the current + * point by the glyph's advance in the direction of the textrun (LTR or RTL). + * Each glyph advance is always rounded to the nearest appunit; this ensures + * consistent results when dividing the text in a textrun into multiple text + * frames (frame boundaries are always aligned to appunits). We optimize + * for the case where a character has a single glyph and zero xoffset and yoffset, + * and the glyph ID and advance are in a reasonable range so we can pack all + * necessary data into 32 bits. + * + * This glyph data is copied into gfxTextRuns as needed from the cache of + * ShapedWords associated with each gfxFont instance. + * + * gfxTextRun methods that measure or draw substrings will associate all the + * glyphs in a cluster with the first character of the cluster; if that character + * is in the substring, the glyphs will be measured or drawn, otherwise they + * won't. + */ +class gfxShapedWord +{ public: - // Flags in the mask 0xFFFF0000 are reserved for textrun clients - // Flags in the mask 0x0000F000 are reserved for per-platform fonts - // Flags in the mask 0x00000FFF are set by the textrun creator. - enum { - CACHE_TEXT_FLAGS = 0xF0000000, - USER_TEXT_FLAGS = 0x0FFF0000, - PLATFORM_TEXT_FLAGS = 0x0000F000, - TEXTRUN_TEXT_FLAGS = 0x00000FFF, - SETTABLE_FLAGS = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS, - - /** - * When set, the text string pointer used to create the text run - * is guaranteed to be available during the lifetime of the text run. - */ - TEXT_IS_PERSISTENT = 0x0001, - /** - * When set, the text is known to be all-ASCII (< 128). - */ - TEXT_IS_ASCII = 0x0002, - /** - * When set, the text is RTL. - */ - TEXT_IS_RTL = 0x0004, - /** - * When set, spacing is enabled and the textrun needs to call GetSpacing - * on the spacing provider. - */ - TEXT_ENABLE_SPACING = 0x0008, + static const PRUint32 kMaxLength = 0x7fff; + + // Create a ShapedWord that can hold glyphs for aLength characters, + // with mCharacterGlyphs sized appropriately. + // + // Returns null on allocation failure (does NOT use infallible alloc) + // so caller must check for success. + // + // This does NOT perform shaping, so the returned word contains no + // glyph data; the caller must call gfxFont::Shape() with appropriate + // parameters to set up the glyphs. + static gfxShapedWord* Create(const PRUint8 *aText, PRUint32 aLength, + PRInt32 aRunScript, + PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) { + NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!"); + + // Compute size needed including the mCharacterGlyphs array + // and a copy of the original text + PRUint32 size = + offsetof(gfxShapedWord, mCharacterGlyphs) + + aLength * (sizeof(CompressedGlyph) + sizeof(PRUint8)); + void *storage = moz_malloc(size); + if (!storage) { + return nsnull; + } + + // Construct in the pre-allocated storage, using placement new + return new (storage) gfxShapedWord(aText, aLength, aRunScript, + aAppUnitsPerDevUnit, aFlags); + } + + static gfxShapedWord* Create(const PRUnichar *aText, PRUint32 aLength, + PRInt32 aRunScript, + PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) { + NS_ASSERTION(aLength <= kMaxLength, "excessive length for gfxShapedWord!"); + + // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set, + // then we convert the text to an 8-bit version and call the 8-bit + // Create function instead. + if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { + nsCAutoString narrowText; + LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), + narrowText); + return Create((const PRUint8*)(narrowText.BeginReading()), + aLength, aRunScript, aAppUnitsPerDevUnit, aFlags); + } + + PRUint32 size = + offsetof(gfxShapedWord, mCharacterGlyphs) + + aLength * (sizeof(CompressedGlyph) + sizeof(PRUnichar)); + void *storage = moz_malloc(size); + if (!storage) { + return nsnull; + } + + return new (storage) gfxShapedWord(aText, aLength, aRunScript, + aAppUnitsPerDevUnit, aFlags); + } + + // Override operator delete to properly free the object that was + // allocated via moz_malloc. + void operator delete(void* p) { + moz_free(p); + } + + /** + * This class records the information associated with a character in the + * input string. It's optimized for the case where there is one glyph + * representing that character alone. + * + * A character can have zero or more associated glyphs. Each glyph + * has an advance width and an x and y offset. + * A character may be the start of a cluster. + * A character may be the start of a ligature group. + * A character can be "missing", indicating that the system is unable + * to render the character. + * + * All characters in a ligature group conceptually share all the glyphs + * associated with the characters in a group. + */ + class CompressedGlyph { + public: + CompressedGlyph() { mValue = 0; } + + enum { + // Indicates that a cluster and ligature group starts at this + // character; this character has a single glyph with a reasonable + // advance and zero offsets. A "reasonable" advance + // is one that fits in the available bits (currently 12) (specified + // in appunits). + FLAG_IS_SIMPLE_GLYPH = 0x80000000U, + + // Indicates whether a linebreak is allowed before this character; + // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value + // indicating the kind of linebreak (if any) allowed here. + FLAGS_CAN_BREAK_BEFORE = 0x60000000U, + + FLAGS_CAN_BREAK_SHIFT = 29, + FLAG_BREAK_TYPE_NONE = 0, + FLAG_BREAK_TYPE_NORMAL = 1, + FLAG_BREAK_TYPE_HYPHEN = 2, + + FLAG_CHAR_IS_SPACE = 0x10000000U, + + // The advance is stored in appunits + ADVANCE_MASK = 0x0FFF0000U, + ADVANCE_SHIFT = 16, + + GLYPH_MASK = 0x0000FFFFU, + + // Non-simple glyphs may or may not have glyph data in the + // corresponding mDetailedGlyphs entry. They have the following + // flag bits: + + // When NOT set, indicates that this character corresponds to a + // missing glyph and should be skipped (or possibly, render the character + // Unicode value in some special way). If there are glyphs, + // the mGlyphID is actually the UTF16 character code. The bit is + // inverted so we can memset the array to zero to indicate all missing. + FLAG_NOT_MISSING = 0x01, + FLAG_NOT_CLUSTER_START = 0x02, + FLAG_NOT_LIGATURE_GROUP_START = 0x04, + + FLAG_CHAR_IS_TAB = 0x08, + FLAG_CHAR_IS_NEWLINE = 0x10, + FLAG_CHAR_IS_LOW_SURROGATE = 0x20, + + GLYPH_COUNT_MASK = 0x00FFFF00U, + GLYPH_COUNT_SHIFT = 8 + }; + + // "Simple glyphs" have a simple glyph ID, simple advance and their + // x and y offsets are zero. Also the glyph extents do not overflow + // the font-box defined by the font ascent, descent and glyph advance width. + // These case is optimized to avoid storing DetailedGlyphs. + + // Returns true if the glyph ID aGlyph fits into the compressed representation + static bool IsSimpleGlyphID(PRUint32 aGlyph) { + return (aGlyph & GLYPH_MASK) == aGlyph; + } + // Returns true if the advance aAdvance fits into the compressed representation. + // aAdvance is in appunits. + static bool IsSimpleAdvance(PRUint32 aAdvance) { + return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; + } + + bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; } + PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; } + PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; } + + bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; } + bool IsClusterStart() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START); + } + bool IsLigatureGroupStart() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START); + } + bool IsLigatureContinuation() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 && + (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) == + (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING); + } + + // Return true if the original character was a normal (breakable, + // trimmable) space (U+0020). Not true for other characters that + // may happen to map to the space glyph (U+00A0). + bool CharIsSpace() const { + return (mValue & FLAG_CHAR_IS_SPACE) != 0; + } + + bool CharIsTab() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0; + } + bool CharIsNewline() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0; + } + bool CharIsLowSurrogate() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0; + } + + void SetClusterStart(bool aIsClusterStart) { + NS_ASSERTION(!IsSimpleGlyph(), + "can't call SetClusterStart on simple glyphs"); + if (aIsClusterStart) { + mValue &= ~FLAG_NOT_CLUSTER_START; + } else { + mValue |= FLAG_NOT_CLUSTER_START; + } + } + + PRUint8 CanBreakBefore() const { + return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT; + } + // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise + PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) { + NS_ASSERTION(aCanBreakBefore <= 2, + "Bogus break-before value!"); + PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT); + PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE); + mValue ^= toggle; + return toggle; + } + + CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) { + NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow"); + NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow"); + mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) | + FLAG_IS_SIMPLE_GLYPH | + (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph; + return *this; + } + CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart, + PRUint32 aGlyphCount) { + mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) | + FLAG_NOT_MISSING | + (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) | + (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) | + (aGlyphCount << GLYPH_COUNT_SHIFT); + return *this; + } /** - * When set, GetHyphenationBreaks may return true for some character - * positions, otherwise it will always return false for all characters. - */ - TEXT_ENABLE_HYPHEN_BREAKS = 0x0010, - /** - * When set, the text has no characters above 255 and it is stored - * in the textrun in 8-bit format. - */ - TEXT_IS_8BIT = 0x0020, - /** - * When set, the RunMetrics::mBoundingBox field will be initialized - * properly based on glyph extents, in particular, glyph extents that - * overflow the standard font-box (the box defined by the ascent, descent - * and advance width of the glyph). When not set, it may just be the - * standard font-box even if glyphs overflow. + * Missing glyphs are treated as ligature group starts; don't mess with + * the cluster-start flag (see bugs 618870 and 619286). */ - TEXT_NEED_BOUNDING_BOX = 0x0040, - /** - * When set, optional ligatures are disabled. Ligatures that are - * required for legible text should still be enabled. - */ - TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080, - /** - * When set, the textrun should favour speed of construction over - * quality. This may involve disabling ligatures and/or kerning or - * other effects. - */ - TEXT_OPTIMIZE_SPEED = 0x0100, - /** - * For internal use by the memory reporter when accounting for - * storage used by textruns. - * Because the reporter may visit each textrun multiple times while - * walking the frame trees and textrun cache, it needs to mark - * textruns that have been seen so as to avoid multiple-accounting. - */ - TEXT_RUN_SIZE_ACCOUNTED = 0x0200 + CompressedGlyph& SetMissing(PRUint32 aGlyphCount) { + mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) | + (aGlyphCount << GLYPH_COUNT_SHIFT); + return *this; + } + PRUint32 GetGlyphCount() const { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT; + } + + void SetIsSpace() { + mValue |= FLAG_CHAR_IS_SPACE; + } + void SetIsTab() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_TAB; + } + void SetIsNewline() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_NEWLINE; + } + void SetIsLowSurrogate() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_LOW_SURROGATE; + } + + private: + PRUint32 mValue; }; /** - * This record contains all the parameters needed to initialize a textrun. + * When the glyphs for a character don't fit into a CompressedGlyph record + * in SimpleGlyph format, we use an array of DetailedGlyphs instead. */ - struct Parameters { - // A reference context suggesting where the textrun will be rendered - gfxContext *mContext; - // Pointer to arbitrary user data (which should outlive the textrun) - void *mUserData; - // A description of which characters have been stripped from the original - // DOM string to produce the characters in the textrun. May be null - // if that information is not relevant. - gfxSkipChars *mSkipChars; - // A list of where linebreaks are currently placed in the textrun. May - // be null if mInitialBreakCount is zero. - PRUint32 *mInitialBreaks; - PRUint32 mInitialBreakCount; - // The ratio to use to convert device pixels to application layout units - PRUint32 mAppUnitsPerDevUnit; + struct DetailedGlyph { + /** The glyphID, or the Unicode character + * if this is a missing glyph */ + PRUint32 mGlyphID; + /** The advance, x-offset and y-offset of the glyph, in appunits + * mAdvance is in the text direction (RTL or LTR) + * mXOffset is always from left to right + * mYOffset is always from top to bottom */ + PRInt32 mAdvance; + float mXOffset, mYOffset; }; - virtual ~gfxTextRunFactory() {} + bool IsClusterStart(PRUint32 aPos) { + NS_ASSERTION(aPos < Length(), "aPos out of range"); + return mCharacterGlyphs[aPos].IsClusterStart(); + } + + bool IsLigatureGroupStart(PRUint32 aPos) { + NS_ASSERTION(aPos < Length(), "aPos out of range"); + return mCharacterGlyphs[aPos].IsLigatureGroupStart(); + } + + PRUint32 Length() const { + return mLength; + } + + const PRUint8* Text8Bit() const { + NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()"); + return reinterpret_cast<const PRUint8*>(&mCharacterGlyphs[Length()]); + } + + const PRUnichar* TextUnicode() const { + NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); + return reinterpret_cast<const PRUnichar*>(&mCharacterGlyphs[Length()]); + } + + PRUnichar GetCharAt(PRUint32 aOffset) const { + NS_ASSERTION(aOffset < Length(), "aOffset out of range"); + return TextIs8Bit() ? + PRUnichar(Text8Bit()[aOffset]) : TextUnicode()[aOffset]; + } + + PRUint32 Flags() const { + return mFlags; + } + + bool IsRightToLeft() const { + return (Flags() & gfxTextRunFactory::TEXT_IS_RTL) != 0; + } + + float GetDirection() const { + return IsRightToLeft() ? -1.0 : 1.0; + } + + bool DisableLigatures() const { + return (Flags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; + } + + bool TextIs8Bit() const { + return (Flags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0; + } + + PRInt32 Script() const { + return mScript; + } + + PRInt32 AppUnitsPerDevUnit() const { + return mAppUnitsPerDevUnit; + } + + void ResetAge() { + mAgeCounter = 0; + } + PRUint32 IncrementAge() { + return ++mAgeCounter; + } + + void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) { + NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here"); + NS_ASSERTION(mCharacterGlyphs, "mCharacterGlyphs pointer is null!"); + mCharacterGlyphs[aCharIndex] = aGlyph; + } + + void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph, + const DetailedGlyph *aGlyphs); + + void SetMissingGlyph(PRUint32 aIndex, PRUint32 aChar, gfxFont *aFont); + + void SetIsSpace(PRUint32 aIndex) { + mCharacterGlyphs[aIndex].SetIsSpace(); + } + + void SetIsLowSurrogate(PRUint32 aIndex) { + SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull); + mCharacterGlyphs[aIndex].SetIsLowSurrogate(); + } + + bool FilterIfIgnorable(PRUint32 aIndex); + + const CompressedGlyph *GetCharacterGlyphs() const { + return &mCharacterGlyphs[0]; + } + + bool HasDetailedGlyphs() const { + return mDetailedGlyphs != nsnull; + } + + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero. + DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) const { + NS_ASSERTION(HasDetailedGlyphs() && + !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() && + mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0, + "invalid use of GetDetailedGlyphs; check the caller!"); + return mDetailedGlyphs->Get(aCharIndex); + } + + void AdjustAdvancesForSyntheticBold(float aSynBoldOffset); + + // this is a public static method in order to make it available + // for gfxTextRun to use directly on its own CompressedGlyph array, + // in addition to the use within ShapedWord + static void + SetupClusterBoundaries(CompressedGlyph *aGlyphs, + const PRUnichar *aString, PRUint32 aLength); + +private: + // so that gfxTextRun can share our DetailedGlyphStore class + friend class gfxTextRun; + + // Construct storage for a ShapedWord, ready to receive glyph data + gfxShapedWord(const PRUint8 *aText, PRUint32 aLength, + PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) + : mLength(aLength) + , mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT) + , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) + , mScript(aRunScript) + , mAgeCounter(0) + { + memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph)); + PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]); + memcpy(text, aText, aLength * sizeof(PRUint8)); + } + + gfxShapedWord(const PRUnichar *aText, PRUint32 aLength, + PRInt32 aRunScript, PRInt32 aAppUnitsPerDevUnit, + PRUint32 aFlags) + : mLength(aLength) + , mFlags(aFlags) + , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) + , mScript(aRunScript) + , mAgeCounter(0) + { + memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph)); + PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]); + memcpy(text, aText, aLength * sizeof(PRUnichar)); + SetupClusterBoundaries(&mCharacterGlyphs[0], aText, aLength); + } + + // Allocate aCount DetailedGlyphs for the given index + DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, + PRUint32 aCount); + + // For characters whose glyph data does not fit the "simple" glyph criteria + // in CompressedGlyph, we use a sorted array to store the association + // between the source character offset and an index into an array + // DetailedGlyphs. The CompressedGlyph record includes a count of + // the number of DetailedGlyph records that belong to the character, + // starting at the given index. + class DetailedGlyphStore { + public: + DetailedGlyphStore() + : mLastUsed(0) + { } + + // This is optimized for the most common calling patterns: + // we rarely need random access to the records, access is most commonly + // sequential through the textRun, so we record the last-used index + // and check whether the caller wants the same record again, or the + // next; if not, it's most likely we're starting over from the start + // of the run, so we check the first entry before resorting to binary + // search as a last resort. + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero + // before calling this, otherwise the assertions here will fire (in a + // debug build), and we'll probably crash. + DetailedGlyph* Get(PRUint32 aOffset) { + NS_ASSERTION(mOffsetToIndex.Length() > 0, + "no detailed glyph records!"); + DetailedGlyph* details = mDetails.Elements(); + // check common cases (fwd iteration, initial entry, etc) first + if (mLastUsed < mOffsetToIndex.Length() - 1 && + aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { + ++mLastUsed; + } else if (aOffset == mOffsetToIndex[0].mOffset) { + mLastUsed = 0; + } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { + // do nothing + } else if (mLastUsed > 0 && + aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { + --mLastUsed; + } else { + mLastUsed = + mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); + } + NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex, + "detailed glyph record missing!"); + return details + mOffsetToIndex[mLastUsed].mIndex; + } + + DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) { + PRUint32 detailIndex = mDetails.Length(); + DetailedGlyph *details = mDetails.AppendElements(aCount); + if (!details) { + return nsnull; + } + // We normally set up glyph records sequentially, so the common case + // here is to append new records to the mOffsetToIndex array; + // test for that before falling back to the InsertElementSorted + // method. + if (mOffsetToIndex.Length() == 0 || + aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { + if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) { + return nsnull; + } + } else { + if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), + CompareRecordOffsets())) { + return nsnull; + } + } + return details; + } + + size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) { + return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) + + mDetails.SizeOfExcludingThis(aMallocSizeOf) + + mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf); + } + + private: + struct DGRec { + DGRec(const PRUint32& aOffset, const PRUint32& aIndex) + : mOffset(aOffset), mIndex(aIndex) { } + PRUint32 mOffset; // source character offset in the textrun + PRUint32 mIndex; // index where this char's DetailedGlyphs begin + }; + + struct CompareToOffset { + bool Equals(const DGRec& a, const PRUint32& b) const { + return a.mOffset == b; + } + bool LessThan(const DGRec& a, const PRUint32& b) const { + return a.mOffset < b; + } + }; + + struct CompareRecordOffsets { + bool Equals(const DGRec& a, const DGRec& b) const { + return a.mOffset == b.mOffset; + } + bool LessThan(const DGRec& a, const DGRec& b) const { + return a.mOffset < b.mOffset; + } + }; + + // Concatenated array of all the DetailedGlyph records needed for the + // textRun; individual character offsets are associated with indexes + // into this array via the mOffsetToIndex table. + nsTArray<DetailedGlyph> mDetails; + + // For each character offset that needs DetailedGlyphs, we record the + // index in mDetails where the list of glyphs begins. This array is + // sorted by mOffset. + nsTArray<DGRec> mOffsetToIndex; + + // Records the most recently used index into mOffsetToIndex, so that + // we can support sequential access more quickly than just doing + // a binary search each time. + nsTArray<DGRec>::index_type mLastUsed; + }; + + nsAutoPtr<DetailedGlyphStore> mDetailedGlyphs; + + // Number of PRUnichar characters and CompressedGlyph glyph records; + // note that gfx font code will never attempt to create a ShapedWord + // with a huge number of characters, so we could limit this to 16 bits + // to minimize memory usage for large numbers of cached words. + PRUint32 mLength; + + PRUint32 mFlags; + + PRInt32 mAppUnitsPerDevUnit; + PRInt32 mScript; + + PRUint32 mAgeCounter; + + // The mCharacterGlyphs array is actually a variable-size member; + // when the ShapedWord is created, its size will be increased as necessary + // to allow the proper number of glyphs to be stored. + // The original text, in either 8-bit or 16-bit form, will be stored + // immediately following the CompressedGlyphs. + CompressedGlyph mCharacterGlyphs[1]; }; /** * gfxTextRun is an abstraction for drawing and measuring substrings of a run * of text. It stores runs of positioned glyph data, each run having a single * gfxFont. The glyphs are associated with a string of source text, and the * gfxTextRun APIs take parameters that are offsets into that source text. * @@ -1438,36 +2162,31 @@ public: * lazily by methods that need it, it is not cached. Line breaks are stored * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does * not actually do anything to explicitly account for line breaks). Initially * there are no line breaks. The textrun can record line breaks before or after * any given cluster. (Line breaks specified inside clusters are ignored.) * * It is important that zero-length substrings are handled correctly. This will * be on the test! - * - * gfxTextRun stores a list of zero or more glyphs for each character. For each - * glyph we store the glyph ID, the advance, and possibly an xoffset and yoffset. - * The idea is that a string is rendered by a loop that draws each glyph - * at its designated offset from the current point, then advances the current - * point by the glyph's advance in the direction of the textrun (LTR or RTL). - * Each glyph advance is always rounded to the nearest appunit; this ensures - * consistent results when dividing the text in a textrun into multiple text - * frames (frame boundaries are always aligned to appunits). We optimize - * for the case where a character has a single glyph and zero xoffset and yoffset, - * and the glyph ID and advance are in a reasonable range so we can pack all - * necessary data into 32 bits. - * - * gfxTextRun methods that measure or draw substrings will associate all the - * glyphs in a cluster with the first character of the cluster; if that character - * is in the substring, the glyphs will be measured or drawn, otherwise they - * won't. */ class THEBES_API gfxTextRun { public: + // we use the same glyph storage as gfxShapedWord, to facilitate copying + // glyph data from shaped words into text runs as needed + typedef gfxShapedWord::CompressedGlyph CompressedGlyph; + typedef gfxShapedWord::DetailedGlyph DetailedGlyph; + typedef gfxShapedWord::DetailedGlyphStore DetailedGlyphStore; + + // Override operator delete to properly free the object that was + // allocated via moz_malloc. + void operator delete(void* p) { + moz_free(p); + } + virtual ~gfxTextRun(); typedef gfxFont::RunMetrics Metrics; // Public textrun API for general use bool IsClusterStart(PRUint32 aPos) { NS_ASSERTION(aPos < mCharacterCount, "aPos out of range"); @@ -1483,16 +2202,33 @@ public: CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; } bool CanHyphenateBefore(PRUint32 aPos) { NS_ASSERTION(aPos < mCharacterCount, "aPos out of range"); return mCharacterGlyphs[aPos].CanBreakBefore() == CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; } + bool CharIsSpace(PRUint32 aPos) { + NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsSpace(); + } + bool CharIsTab(PRUint32 aPos) { + NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsTab(); + } + bool CharIsNewline(PRUint32 aPos) { + NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsNewline(); + } + bool CharIsLowSurrogate(PRUint32 aPos) { + NS_ASSERTION(0 <= aPos && aPos < mCharacterCount, "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsLowSurrogate(); + } + PRUint32 GetLength() { return mCharacterCount; } // All PRUint32 aStart, PRUint32 aLength ranges below are restricted to // grapheme cluster boundaries! All offsets are in terms of the string // passed into MakeTextRun. // All coordinates are in layout/app units @@ -1758,191 +2494,23 @@ public: void ClearFlagBits(PRUint32 aFlags) { NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS), "Only user flags should be mutable"); mFlags &= ~aFlags; } const gfxSkipChars& GetSkipChars() const { return mSkipChars; } PRUint32 GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; } gfxFontGroup *GetFontGroup() const { return mFontGroup; } - const PRUint8 *GetText8Bit() const - { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle : nsnull; } - const PRUnichar *GetTextUnicode() const - { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? nsnull : mText.mDouble; } - const void *GetTextAt(PRUint32 aIndex) { - return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) - ? static_cast<const void *>(mText.mSingle + aIndex) - : static_cast<const void *>(mText.mDouble + aIndex); - } - const PRUnichar GetChar(PRUint32 i) const - { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle[i] : mText.mDouble[i]; } - PRUint32 GetHashCode() const { return mHashCode; } - void SetHashCode(PRUint32 aHash) { mHashCode = aHash; } + // Call this, don't call "new gfxTextRun" directly. This does custom // allocation and initialization static gfxTextRun *Create(const gfxTextRunFactory::Parameters *aParams, const void *aText, PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags); - /** - * This class records the information associated with a character in the - * input string. It's optimized for the case where there is one glyph - * representing that character alone. - * - * A character can have zero or more associated glyphs. Each glyph - * has an advance width and an x and y offset. - * A character may be the start of a cluster. - * A character may be the start of a ligature group. - * A character can be "missing", indicating that the system is unable - * to render the character. - * - * All characters in a ligature group conceptually share all the glyphs - * associated with the characters in a group. - */ - class CompressedGlyph { - public: - CompressedGlyph() { mValue = 0; } - - enum { - // Indicates that a cluster and ligature group starts at this - // character; this character has a single glyph with a reasonable - // advance and zero offsets. A "reasonable" advance - // is one that fits in the available bits (currently 13) (specified - // in appunits). - FLAG_IS_SIMPLE_GLYPH = 0x80000000U, - - // Indicates whether a linebreak is allowed before this character; - // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value - // indicating the kind of linebreak (if any) allowed here. - FLAGS_CAN_BREAK_BEFORE = 0x60000000U, - - FLAGS_CAN_BREAK_SHIFT = 29, - FLAG_BREAK_TYPE_NONE = 0, - FLAG_BREAK_TYPE_NORMAL = 1, - FLAG_BREAK_TYPE_HYPHEN = 2, - - // The advance is stored in appunits - ADVANCE_MASK = 0x1FFF0000U, - ADVANCE_SHIFT = 16, - - GLYPH_MASK = 0x0000FFFFU, - - // Non-simple glyphs may or may not have glyph data in the - // corresponding mDetailedGlyphs entry. They have the following - // flag bits: - - // When NOT set, indicates that this character corresponds to a - // missing glyph and should be skipped (or possibly, render the character - // Unicode value in some special way). If there are glyphs, - // the mGlyphID is actually the UTF16 character code. The bit is - // inverted so we can memset the array to zero to indicate all missing. - FLAG_NOT_MISSING = 0x01, - FLAG_NOT_CLUSTER_START = 0x02, - FLAG_NOT_LIGATURE_GROUP_START = 0x04, - - GLYPH_COUNT_MASK = 0x00FFFF00U, - GLYPH_COUNT_SHIFT = 8 - }; - - // "Simple glyphs" have a simple glyph ID, simple advance and their - // x and y offsets are zero. Also the glyph extents do not overflow - // the font-box defined by the font ascent, descent and glyph advance width. - // These case is optimized to avoid storing DetailedGlyphs. - - // Returns true if the glyph ID aGlyph fits into the compressed representation - static bool IsSimpleGlyphID(PRUint32 aGlyph) { - return (aGlyph & GLYPH_MASK) == aGlyph; - } - // Returns true if the advance aAdvance fits into the compressed representation. - // aAdvance is in appunits. - static bool IsSimpleAdvance(PRUint32 aAdvance) { - return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; - } - - bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; } - PRUint32 GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; } - PRUint32 GetSimpleGlyph() const { return mValue & GLYPH_MASK; } - - bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; } - bool IsClusterStart() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START); - } - bool IsLigatureGroupStart() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START); - } - bool IsLigatureContinuation() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 && - (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) == - (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING); - } - - PRUint8 CanBreakBefore() const { - return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT; - } - // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise - PRUint32 SetCanBreakBefore(PRUint8 aCanBreakBefore) { - NS_ASSERTION(aCanBreakBefore <= 2, - "Bogus break-before value!"); - PRUint32 breakMask = (PRUint32(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT); - PRUint32 toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE); - mValue ^= toggle; - return toggle; - } - - CompressedGlyph& SetSimpleGlyph(PRUint32 aAdvanceAppUnits, PRUint32 aGlyph) { - NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow"); - NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow"); - mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) | - FLAG_IS_SIMPLE_GLYPH | - (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph; - return *this; - } - CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart, - PRUint32 aGlyphCount) { - mValue = (mValue & FLAGS_CAN_BREAK_BEFORE) | - FLAG_NOT_MISSING | - (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) | - (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) | - (aGlyphCount << GLYPH_COUNT_SHIFT); - return *this; - } - /** - * Missing glyphs are treated as ligature group starts; don't mess with - * the cluster-start flag (see bugs 618870 and 619286). - */ - CompressedGlyph& SetMissing(PRUint32 aGlyphCount) { - mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START)) | - (aGlyphCount << GLYPH_COUNT_SHIFT); - return *this; - } - PRUint32 GetGlyphCount() const { - NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); - return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT; - } - - private: - PRUint32 mValue; - }; - - /** - * When the glyphs for a character don't fit into a CompressedGlyph record - * in SimpleGlyph format, we use an array of DetailedGlyphs instead. - */ - struct DetailedGlyph { - /** The glyphID, or the Unicode character - * if this is a missing glyph */ - PRUint32 mGlyphID; - /** The advance, x-offset and y-offset of the glyph, in appunits - * mAdvance is in the text direction (RTL or LTR) - * mXOffset is always from left to right - * mYOffset is always from top to bottom */ - PRInt32 mAdvance; - float mXOffset, mYOffset; - }; - // The text is divided into GlyphRuns as necessary struct GlyphRun { nsRefPtr<gfxFont> mFont; // never null PRUint32 mCharacterOffset; // into original UTF16 string PRUint8 mMatchType; }; class THEBES_API GlyphRunIterator { @@ -2001,47 +2569,90 @@ public: nsresult AddGlyphRun(gfxFont *aFont, PRUint8 aMatchType, PRUint32 aStartCharIndex, bool aForceNewRun); void ResetGlyphRuns() { mGlyphRuns.Clear(); } void SortGlyphRuns(); void SanitizeGlyphRuns(); // Call the following glyph-setters during initialization or during reshaping // only. It is OK to overwrite existing data for a character. + void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) { + NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here"); + mCharacterGlyphs[aCharIndex] = aGlyph; + } /** * Set the glyph data for a character. aGlyphs may be null if aGlyph is a * simple glyph or has no associated glyphs. If non-null the data is copied, * the caller retains ownership. */ - void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) { - NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here"); - if (mCharacterGlyphs) { - mCharacterGlyphs[aCharIndex] = aGlyph; - } - } void SetGlyphs(PRUint32 aCharIndex, CompressedGlyph aGlyph, const DetailedGlyph *aGlyphs); void SetMissingGlyph(PRUint32 aCharIndex, PRUint32 aUnicodeChar); void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, PRUint32 aCharIndex); - // If the character at aIndex is default-ignorable, set the glyph - // to be invisible-missing and return TRUE, else return FALSE - bool FilterIfIgnorable(PRUint32 aIndex); + // Set the glyph data for the given character index to the font's + // space glyph, IF this can be done as a "simple" glyph record + // (not requiring a DetailedGlyph entry). This avoids the need to call + // the font shaper and go through the shaped-word cache for most spaces. + // + // The parameter aSpaceChar is the original character code for which + // this space glyph is being used; if this is U+0020, we need to record + // that it could be trimmed at a run edge, whereas other kinds of space + // (currently just U+00A0) would not be trimmable/breakable. + // + // Returns true if it was able to set simple glyph data for the space; + // if it returns false, the caller needs to fall back to some other + // means to create the necessary (detailed) glyph data. + bool SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, + PRUint32 aCharIndex, PRUnichar aSpaceChar); + + // Record the positions of specific characters that layout may need to + // detect in the textrun, even though it doesn't have an explicit copy + // of the original text. These are recorded using flag bits in the + // CompressedGlyph record; if necessary, we convert "simple" glyph records + // to "complex" ones as the Tab and Newline flags are not present in + // simple CompressedGlyph records. + void SetIsTab(PRUint32 aIndex) { + CompressedGlyph *g = &mCharacterGlyphs[aIndex]; + if (g->IsSimpleGlyph()) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + details->mGlyphID = g->GetSimpleGlyph(); + details->mAdvance = g->GetSimpleAdvance(); + details->mXOffset = details->mYOffset = 0; + SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); + } + g->SetIsTab(); + } + void SetIsNewline(PRUint32 aIndex) { + CompressedGlyph *g = &mCharacterGlyphs[aIndex]; + if (g->IsSimpleGlyph()) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + details->mGlyphID = g->GetSimpleGlyph(); + details->mAdvance = g->GetSimpleAdvance(); + details->mXOffset = details->mYOffset = 0; + SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); + } + g->SetIsNewline(); + } + void SetIsLowSurrogate(PRUint32 aIndex) { + SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nsnull); + mCharacterGlyphs[aIndex].SetIsLowSurrogate(); + } /** * Prefetch all the glyph extents needed to ensure that Measure calls * on this textrun not requesting tight boundingBoxes will succeed. Note * that some glyph extents might not be fetched due to OOM or other * errors. */ void FetchGlyphExtents(gfxContext *aRefContext); // API for access to the raw glyph data, needed by gfxFont::Draw // and gfxFont::GetBoundingBox - const CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; } + CompressedGlyph *GetCharacterGlyphs() { return mCharacterGlyphs; } // NOTE that this must not be called for a character offset that does // not have any DetailedGlyph records; callers must have verified that // mCharacterGlyphs[aCharIndex].GetGlyphCount() is greater than zero. DetailedGlyph *GetDetailedGlyphs(PRUint32 aCharIndex) { NS_ASSERTION(mDetailedGlyphs != nsnull && !mCharacterGlyphs[aCharIndex].IsSimpleGlyph() && mCharacterGlyphs[aCharIndex].GetGlyphCount() > 0, @@ -2053,20 +2664,24 @@ public: PRUint32 CountMissingGlyphs(); const GlyphRun *GetGlyphRuns(PRUint32 *aNumGlyphRuns) { *aNumGlyphRuns = mGlyphRuns.Length(); return mGlyphRuns.Elements(); } // Returns the index of the GlyphRun containing the given offset. // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset); + + // Copy glyph data from a ShapedWord into this textrun. + void CopyGlyphDataFrom(const gfxShapedWord *aSource, PRUint32 aStart); + // Copy glyph data for a range of characters from aSource to this // textrun. - virtual void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart, - PRUint32 aLength, PRUint32 aDest); + void CopyGlyphDataFrom(gfxTextRun *aSource, PRUint32 aStart, + PRUint32 aLength, PRUint32 aDest); nsExpirationState *GetExpirationState() { return &mExpirationState; } struct LigatureData { // textrun offsets of the start and end of the containing ligature PRUint32 mLigatureStart; PRUint32 mLigatureEnd; // appunits advance to the start of the ligature part within the ligature; @@ -2076,19 +2691,16 @@ public: // when the part is at the start of the ligature, and after-spacing // when the part is as the end of the ligature gfxFloat mPartWidth; bool mClipBeforePart; bool mClipAfterPart; }; - // user font set generation when text run was created - PRUint64 GetUserFontSetGeneration() { return mUserFontSetGeneration; } - // return storage used by this run, for memory reporter; // nsTransformedTextRun needs to override this as it holds additional data virtual NS_MUST_OVERRIDE size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf); virtual NS_MUST_OVERRIDE size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf); // Get the size, if it hasn't already been gotten, marking as it goes. @@ -2099,49 +2711,41 @@ public: mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; return SizeOfIncludingThis(aMallocSizeOf); } void ResetSizeOfAccountingFlags() { mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; } #ifdef DEBUG - // number of entries referencing this textrun in the gfxTextRunWordCache - PRUint32 mCachedWords; - // generation of gfxTextRunWordCache that refers to this textrun; - // if the cache gets cleared, then mCachedWords is no longer meaningful - PRUint32 mCacheGeneration; - void Dump(FILE* aOutput); #endif - // post-process glyph advances to deal with synthetic bolding - void AdjustAdvancesForSyntheticBold(gfxContext *aContext, - PRUint32 aStart, - PRUint32 aLength); - protected: /** - * Initializes the textrun to blank. - * @param aGlyphStorage preallocated array of CompressedGlyph[aLength] - * for the textrun to use; if aText is not persistent, then it has also - * been appended to this array, so it must NOT be freed separately. + * Create a textrun, and set its mCharacterGlyphs to point immediately + * after the base object; this is ONLY used in conjunction with placement + * new, after allocating a block large enough for the glyph records to + * follow the base textrun object. */ gfxTextRun(const gfxTextRunFactory::Parameters *aParams, const void *aText, - PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags, - CompressedGlyph *aGlyphStorage); + PRUint32 aLength, gfxFontGroup *aFontGroup, PRUint32 aFlags); /** * Helper for the Create() factory method to allocate the required - * glyph storage, and copy the text (modifying the aText parameter) - * if it is not flagged as persistent. + * glyph storage for a textrun object with the basic size aSize, + * plus room for aLength glyph records. */ - static CompressedGlyph* AllocateStorage(const void*& aText, - PRUint32 aLength, - PRUint32 aFlags); + static void* AllocateStorageForTextRun(size_t aSize, PRUint32 aLength); + + // All our glyph data is in logical order, not visual. + // Space for mCharacterGlyphs is allocated fused with the textrun object, + // and then the constructor sets the pointer to the beginning of this + // storage area. Thus, this pointer must NOT be freed! + CompressedGlyph *mCharacterGlyphs; private: // **** general helpers **** // Allocate aCount DetailedGlyphs for the given index DetailedGlyph *AllocateDetailedGlyphs(PRUint32 aCharIndex, PRUint32 aCount); // Get the total advance for a range of glyphs. @@ -2189,166 +2793,29 @@ private: Metrics *aMetrics); // **** drawing helper **** void DrawGlyphs(gfxFont *aFont, gfxContext *aContext, bool aDrawToPath, gfxPoint *aPt, PRUint32 aStart, PRUint32 aEnd, PropertyProvider *aProvider, PRUint32 aSpacingStart, PRUint32 aSpacingEnd); - // All our glyph data is in logical order, not visual. - // mCharacterGlyphs is allocated by the factory that creates the textrun, - // to avoid the possibility of failure during the constructor; - // however, ownership passes to the textrun during construction and so - // it must be deleted in the destructor. - CompressedGlyph* mCharacterGlyphs; - - // For characters whose glyph data does not fit the "simple" glyph criteria - // in CompressedGlyph, we use a sorted array to store the association - // between the source character offset and an index into an array - // DetailedGlyphs. The CompressedGlyph record includes a count of - // the number of DetailedGlyph records that belong to the character, - // starting at the given index. - class DetailedGlyphStore { - public: - DetailedGlyphStore() - : mLastUsed(0) - { } - - // This is optimized for the most common calling patterns: - // we rarely need random access to the records, access is most commonly - // sequential through the textRun, so we record the last-used index - // and check whether the caller wants the same record again, or the - // next; if not, it's most likely we're starting over from the start - // of the run, so we check the first entry before resorting to binary - // search as a last resort. - // NOTE that this must not be called for a character offset that does - // not have any DetailedGlyph records; callers must have verified that - // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero - // before calling this, otherwise the assertions here will fire (in a - // debug build), and we'll probably crash. - DetailedGlyph* Get(PRUint32 aOffset) { - NS_ASSERTION(mOffsetToIndex.Length() > 0, - "no detailed glyph records!"); - DetailedGlyph* details = mDetails.Elements(); - // check common cases (fwd iteration, initial entry, etc) first - if (mLastUsed < mOffsetToIndex.Length() - 1 && - aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { - ++mLastUsed; - } else if (aOffset == mOffsetToIndex[0].mOffset) { - mLastUsed = 0; - } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { - // do nothing - } else if (mLastUsed > 0 && - aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { - --mLastUsed; - } else { - mLastUsed = - mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); - } - NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex, - "detailed glyph record missing!"); - return details + mOffsetToIndex[mLastUsed].mIndex; - } - - DetailedGlyph* Allocate(PRUint32 aOffset, PRUint32 aCount) { - PRUint32 detailIndex = mDetails.Length(); - DetailedGlyph *details = mDetails.AppendElements(aCount); - if (!details) { - return nsnull; - } - // We normally set up glyph records sequentially, so the common case - // here is to append new records to the mOffsetToIndex array; - // test for that before falling back to the InsertElementSorted - // method. - if (mOffsetToIndex.Length() == 0 || - aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { - if (!mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex))) { - return nsnull; - } - } else { - if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), - CompareRecordOffsets())) { - return nsnull; - } - } - return details; - } - - size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) { - return aMallocSizeOf(this, sizeof(DetailedGlyphStore)) + - mDetails.SizeOfExcludingThis(aMallocSizeOf) + - mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf); - } - - private: - struct DGRec { - DGRec(const PRUint32& aOffset, const PRUint32& aIndex) - : mOffset(aOffset), mIndex(aIndex) { } - PRUint32 mOffset; // source character offset in the textrun - PRUint32 mIndex; // index where this char's DetailedGlyphs begin - }; - - struct CompareToOffset { - bool Equals(const DGRec& a, const PRUint32& b) const { - return a.mOffset == b; - } - bool LessThan(const DGRec& a, const PRUint32& b) const { - return a.mOffset < b; - } - }; - - struct CompareRecordOffsets { - bool Equals(const DGRec& a, const DGRec& b) const { - return a.mOffset == b.mOffset; - } - bool LessThan(const DGRec& a, const DGRec& b) const { - return a.mOffset < b.mOffset; - } - }; - - // Concatenated array of all the DetailedGlyph records needed for the - // textRun; individual character offsets are associated with indexes - // into this array via the mOffsetToIndex table. - nsTArray<DetailedGlyph> mDetails; - - // For each character offset that needs DetailedGlyphs, we record the - // index in mDetails where the list of glyphs begins. This array is - // sorted by mOffset. - nsTArray<DGRec> mOffsetToIndex; - - // Records the most recently used index into mOffsetToIndex, so that - // we can support sequential access more quickly than just doing - // a binary search each time. - nsTArray<DGRec>::index_type mLastUsed; - }; - nsAutoPtr<DetailedGlyphStore> mDetailedGlyphs; // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*, // for smaller size especially in the super-common one-glyphrun case nsAutoTArray<GlyphRun,1> mGlyphRuns; - // When TEXT_IS_8BIT is set, we use mSingle, otherwise we use mDouble. - // When TEXT_IS_PERSISTENT is set, we don't own the text, otherwise we - // own the text. When we own the text, it's allocated fused with the - // mCharacterGlyphs array, and therefore need not be explicitly deleted. - // This text is not null-terminated. - union { - const PRUint8 *mSingle; - const PRUnichar *mDouble; - } mText; + void *mUserData; gfxFontGroup *mFontGroup; // addrefed gfxSkipChars mSkipChars; nsExpirationState mExpirationState; PRUint32 mAppUnitsPerDevUnit; PRUint32 mFlags; PRUint32 mCharacterCount; - PRUint32 mHashCode; - PRUint64 mUserFontSetGeneration; // user font set generation when text run created bool mSkipDrawing; // true if the font group we used had a user font // download that's in progress, so we should hide text // until the download completes (or timeout fires) }; class THEBES_API gfxFontGroup : public gfxTextRunFactory { public: @@ -2380,31 +2847,21 @@ public: mStyle.Equals(other.mStyle); } const gfxFontStyle *GetStyle() const { return &mStyle; } virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle); /** - * The listed characters should not be passed in to MakeTextRun and should - * be treated as invisible and zero-width. + * The listed characters should be treated as invisible and zero-width + * when creating textruns. */ + static bool IsInvalidChar(PRUint8 ch); static bool IsInvalidChar(PRUnichar ch); - - /** - * Make a textrun for an empty string. This is fast; if you call it, - * don't bother caching the result. - */ - gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags); - /** - * Make a textrun for a single ASCII space. This is fast; if you call it, - * don't bother caching the result. - */ - gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags); /** * Make a textrun for a given string. * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the * textrun will copy it. * This calls FetchGlyphExtents on the textrun. */ virtual gfxTextRun *MakeTextRun(const PRUnichar *aString, PRUint32 aLength, @@ -2413,16 +2870,32 @@ public: * Make a textrun for a given string. * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the * textrun will copy it. * This calls FetchGlyphExtents on the textrun. */ virtual gfxTextRun *MakeTextRun(const PRUint8 *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags); + /** + * Textrun creation helper for clients that don't want to pass + * a full Parameters record. + */ + template<typename T> + gfxTextRun *MakeTextRun(const T *aString, PRUint32 aLength, + gfxContext *aRefContext, + PRUint32 aAppUnitsPerDevUnit, + PRUint32 aFlags) + { + gfxTextRunFactory::Parameters params = { + aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit + }; + return MakeTextRun(aString, aLength, ¶ms, aFlags); + } + /* helper function for splitting font families on commas and * calling a function for each family to fill the mFonts array */ typedef bool (*FontCreationCallback) (const nsAString& aName, const nsACString& aGenericName, bool aUseFontSet, void *closure); bool ForEachFont(const nsAString& aFamilies, @@ -2456,18 +2929,19 @@ public: gfxFont *aPrevMatchedFont, PRUint8 *aMatchType); // search through pref fonts for a character, return nsnull if no matching pref font virtual already_AddRefed<gfxFont> WhichPrefFontSupportsChar(PRUint32 aCh); virtual already_AddRefed<gfxFont> WhichSystemFontSupportsChar(PRUint32 aCh); + template<typename T> void ComputeRanges(nsTArray<gfxTextRange>& mRanges, - const PRUnichar *aString, PRUint32 begin, PRUint32 end, + const T *aString, PRUint32 aLength, PRInt32 aRunScript); gfxUserFontSet* GetUserFontSet(); // With downloadable fonts, the composition of the font group can change as fonts are downloaded // for each change in state of the user font set, the generation value is bumped to avoid picking up // previously created text runs in the text run word cache. For font groups based on stylesheets // with no @font-face rule, this always returns 0. @@ -2496,41 +2970,51 @@ protected: eFontPrefLang mLastPrefLang; // lang group for last pref font eFontPrefLang mPageLang; bool mLastPrefFirstFont; // is this the first font in the list of pref fonts for this lang group? bool mSkipDrawing; // hide text while waiting for a font // download to complete (or fallback // timer to fire) + /** + * Textrun creation short-cuts for special cases where we don't need to + * call a font shaper to generate glyphs. + */ + gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags); + gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, PRUint32 aFlags); + gfxTextRun *MakeBlankTextRun(const void* aText, PRUint32 aLength, + const Parameters *aParams, PRUint32 aFlags); + // Used for construction/destruction. Not intended to change the font set // as invalidation of font lists and caches is not considered. void SetUserFontSet(gfxUserFontSet *aUserFontSet); // Initialize the list of fonts void BuildFontList(); // Init this font group's font metrics. If there no bad fonts, you don't need to call this. // But if there are one or more bad fonts which have bad underline offset, // you should call this with the *first* bad font. void InitMetricsForBadFont(gfxFont* aBadFont); // Set up the textrun glyphs for an entire text run: // find script runs, and then call InitScriptRun for each + template<typename T> void InitTextRun(gfxContext *aContext, gfxTextRun *aTextRun, - const PRUnichar *aString, + const T *aString, PRUint32 aLength); // InitTextRun helper to handle a single script run, by finding font ranges // and calling each font's InitTextRun() as appropriate + template<typename T> void InitScriptRun(gfxContext *aContext, gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aTotalLength, + const T *aString, PRUint32 aScriptRunStart, PRUint32 aScriptRunEnd, PRInt32 aRunScript); /* If aResolveGeneric is true, then CSS/Gecko generic family names are * replaced with preferred fonts. * * If aResolveFontName is true then fc() is called only for existing fonts
--- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -114,48 +114,43 @@ gfxGDIFont::CreatePlatformShaper() gfxFont* gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption) { return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()), &mStyle, mNeedsBold, anAAOption); } static bool -UseUniscribe(gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength) +UseUniscribe(gfxShapedWord *aShapedWord, + const PRUnichar *aString) { - PRUint32 flags = aTextRun->GetFlags(); + PRUint32 flags = aShapedWord->Flags(); bool useGDI; bool isXP = (gfxWindowsPlatform::WindowsOSVersion() < gfxWindowsPlatform::kWindowsVista); // bug 561304 - Uniscribe bug produces bad positioning at certain // font sizes on XP, so default to GDI on XP using logic of 3.6 useGDI = isXP && (flags & (gfxTextRunFactory::TEXT_OPTIMIZE_SPEED | gfxTextRunFactory::TEXT_IS_RTL) ) == gfxTextRunFactory::TEXT_OPTIMIZE_SPEED; return !useGDI || - ScriptIsComplex(aString + aRunStart, aRunLength, SIC_COMPLEX) == S_OK; + ScriptIsComplex(aString, aShapedWord->Length(), SIC_COMPLEX) == S_OK; } bool -gfxGDIFont::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping) +gfxGDIFont::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString, + bool aPreferPlatformShaping) { if (!mMetrics) { Initialize(); } if (!mIsValid) { NS_WARNING("invalid font! expect incorrect text rendering"); return false; } @@ -163,81 +158,66 @@ gfxGDIFont::InitTextRun(gfxContext *aCon bool ok = false; // ensure the cairo font is set up, so there's no risk it'll fall back to // creating a "toy" font internally (see bug 544617) SetupCairoFont(aContext); #ifdef MOZ_GRAPHITE if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { - ok = mGraphiteShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mGraphiteShaper->ShapeWord(aContext, aShapedWord, aString); } #endif if (!ok && mHarfBuzzShaper) { - if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aRunScript)) { - ok = mHarfBuzzShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aShapedWord->Script())) { + ok = mHarfBuzzShaper->ShapeWord(aContext, aShapedWord, aString); } } if (!ok) { GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry()); bool preferUniscribe = (!fe->IsTrueType() || fe->IsSymbolFont()) && !fe->mForceGDI; - if (preferUniscribe || - UseUniscribe(aTextRun, aString, aRunStart, aRunLength)) - { + if (preferUniscribe || UseUniscribe(aShapedWord, aString)) { // first try Uniscribe if (!mUniscribeShaper) { mUniscribeShaper = new gfxUniscribeShaper(this); } - ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString); if (ok) { return true; } // fallback to GDI shaping if (!mPlatformShaper) { CreatePlatformShaper(); } - ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString); } else { // first use GDI if (!mPlatformShaper) { CreatePlatformShaper(); } - ok = mPlatformShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); - + ok = mPlatformShaper->ShapeWord(aContext, aShapedWord, aString); if (ok) { return true; } // try Uniscribe if GDI failed if (!mUniscribeShaper) { mUniscribeShaper = new gfxUniscribeShaper(this); } // use Uniscribe shaping - ok = mUniscribeShaper->InitTextRun(aContext, aTextRun, aString, - aRunStart, aRunLength, - aRunScript); + ok = mUniscribeShaper->ShapeWord(aContext, aShapedWord, aString); } #if DEBUG if (!ok) { NS_ConvertUTF16toUTF8 name(GetName()); char msg[256]; sprintf(msg,
--- a/gfx/thebes/gfxGDIFont.h +++ b/gfx/thebes/gfxGDIFont.h @@ -87,23 +87,20 @@ public: // get hinted glyph width in pixels as 16.16 fixed-point value virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID); protected: virtual void CreatePlatformShaper(); /* override to check for uniscribe failure and fall back to GDI */ - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript, - bool aPreferPlatformShaping = false); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString, + bool aPreferPlatformShaping = false); void Initialize(); // creates metrics and Cairo fonts void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize); // mPlatformShaper is used for the GDI shaper, mUniscribeShaper // for the Uniscribe version if needed nsAutoPtr<gfxFontShaper> mUniscribeShaper;
--- a/gfx/thebes/gfxGDIShaper.cpp +++ b/gfx/thebes/gfxGDIShaper.cpp @@ -45,86 +45,84 @@ /********************************************************************** * * class gfxGDIShaper * **********************************************************************/ bool -gfxGDIShaper::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +gfxGDIShaper::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString) { DCFromContext dc(aContext); AutoSelectFont selectFont(dc, static_cast<gfxGDIFont*>(mFont)->GetHFONT()); + PRUint32 length = aShapedWord->Length(); nsAutoTArray<WORD,500> glyphArray; - if (!glyphArray.SetLength(aRunLength)) { + if (!glyphArray.SetLength(length)) { return false; } WORD *glyphs = glyphArray.Elements(); - DWORD ret = ::GetGlyphIndicesW(dc, aString + aRunStart, aRunLength, + DWORD ret = ::GetGlyphIndicesW(dc, aString, length, glyphs, GGI_MARK_NONEXISTING_GLYPHS); if (ret == GDI_ERROR) { return false; } - for (int k = 0; k < aRunLength; k++) { + for (int k = 0; k < length; k++) { if (glyphs[k] == 0xFFFF) return false; } SIZE size; nsAutoTArray<int,500> partialWidthArray; - if (!partialWidthArray.SetLength(aRunLength)) { + if (!partialWidthArray.SetLength(length)) { return false; } BOOL success = ::GetTextExtentExPointI(dc, glyphs, - aRunLength, + length, INT_MAX, NULL, partialWidthArray.Elements(), &size); if (!success) { return false; } gfxTextRun::CompressedGlyph g; PRUint32 i; PRInt32 lastWidth = 0; - PRUint32 appUnitsPerDevPixel = aTextRun->GetAppUnitsPerDevUnit(); - for (i = 0; i < aRunLength; ++i) { - PRUint32 offset = aRunStart + i; + PRUint32 appUnitsPerDevPixel = aShapedWord->AppUnitsPerDevUnit(); + for (i = 0; i < length; ++i) { + PRUint32 offset = i; PRInt32 advancePixels = partialWidthArray[i] - lastWidth; lastWidth = partialWidthArray[i]; - PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel; + PRInt32 advanceAppUnits = advancePixels * appUnitsPerDevPixel; WCHAR glyph = glyphs[i]; - NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aTextRun->GetChar(offset)), + NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aShapedWord->GetCharAt(offset)), "Invalid character detected!"); - bool atClusterStart = aTextRun->IsClusterStart(offset); + bool atClusterStart = aShapedWord->IsClusterStart(offset); if (advanceAppUnits >= 0 && - gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) && - gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph) && + gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) && + gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(glyph) && atClusterStart) { - aTextRun->SetSimpleGlyph(offset, - g.SetSimpleGlyph(advanceAppUnits, glyph)); + aShapedWord->SetSimpleGlyph(offset, + g.SetSimpleGlyph(advanceAppUnits, glyph)); } else { - gfxTextRun::DetailedGlyph details; + gfxShapedWord::DetailedGlyph details; details.mGlyphID = glyph; details.mAdvance = advanceAppUnits; details.mXOffset = 0; details.mYOffset = 0; - aTextRun->SetGlyphs(offset, - g.SetComplex(atClusterStart, true, 1), - &details); + aShapedWord->SetGlyphs(offset, + g.SetComplex(atClusterStart, true, 1), + &details); } } return true; }
--- a/gfx/thebes/gfxGDIShaper.h +++ b/gfx/thebes/gfxGDIShaper.h @@ -49,17 +49,14 @@ public: MOZ_COUNT_CTOR(gfxGDIShaper); } virtual ~gfxGDIShaper() { MOZ_COUNT_DTOR(gfxGDIShaper); } - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aString); }; #endif /* GFX_GDISHAPER_H */
--- a/gfx/thebes/gfxGraphiteShaper.cpp +++ b/gfx/thebes/gfxGraphiteShaper.cpp @@ -156,22 +156,19 @@ MakeGraphiteLangTag(PRUint32 aTag) while ((grLangTag & mask) == ' ') { grLangTag &= ~mask; mask <<= 8; } return grLangTag; } bool -gfxGraphiteShaper::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +gfxGraphiteShaper::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText) { // some font back-ends require this in order to get proper hinted metrics mFont->SetupCairoFont(aContext); mCallbackData.mContext = aContext; if (!mGrFont) { mGrFace = gr_make_face(&mCallbackData, GrGetTable, gr_face_default); @@ -185,73 +182,61 @@ gfxGraphiteShaper::InitTextRun(gfxContex gr_make_font(mFont->GetAdjustedSize(), mGrFace); if (!mGrFont) { gr_face_destroy(mGrFace); mGrFace = nsnull; return false; } } - const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle(); + gfxFontEntry *entry = mFont->GetFontEntry(); + const gfxFontStyle *style = mFont->GetStyle(); PRUint32 grLang = 0; if (style->languageOverride) { grLang = MakeGraphiteLangTag(style->languageOverride); - } else if (mFont->GetFontEntry()->mLanguageOverride) { - grLang = MakeGraphiteLangTag(mFont->GetFontEntry()->mLanguageOverride); + } else if (entry->mLanguageOverride) { + grLang = MakeGraphiteLangTag(entry->mLanguageOverride); } else { nsCAutoString langString; style->language->ToUTF8String(langString); grLang = GetGraphiteTagForLang(langString); } gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang); - bool disableLigatures = - (aTextRun->GetFlags() & - gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; - if (disableLigatures) { + if (aShapedWord->DisableLigatures()) { const gr_feature_ref* fref = gr_face_find_fref(mGrFace, TRUETYPE_TAG('l','i','g','a')); if (fref) { gr_fref_set_feature_value(fref, 0, grFeatures); } } const nsTArray<gfxFontFeature> *features = &style->featureSettings; if (features->IsEmpty()) { - features = &mFont->GetFontEntry()->mFeatureSettings; + features = &entry->mFeatureSettings; } for (PRUint32 i = 0; i < features->Length(); ++i) { const gr_feature_ref* fref = gr_face_find_fref(mGrFace, (*features)[i].mTag); if (fref) { gr_fref_set_feature_value(fref, (*features)[i].mValue, grFeatures); } } - const PRUnichar *textStart = aString + aRunStart; - const PRUnichar *textEnd = textStart + aRunLength; - const void *pError; - size_t nChars = gr_count_unicode_characters(gr_utf16, - textStart, textEnd, - &pError); - if (pError != nsnull) { - return false; - } - gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures, - gr_utf16, textStart, nChars, - aTextRun->IsRightToLeft()); + gr_utf16, aText, aShapedWord->Length(), + aShapedWord->IsRightToLeft()); if (features) { gr_featureval_destroy(grFeatures); } if (!seg) { return false; } - nsresult rv = SetGlyphsFromSegment(aTextRun, aRunStart, aRunLength, seg); + nsresult rv = SetGlyphsFromSegment(aShapedWord, seg); gr_seg_destroy(seg); return NS_SUCCEEDED(rv); } #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays // for short (typical) runs up to this length @@ -260,33 +245,31 @@ struct Cluster { PRUint32 baseChar; PRUint32 baseGlyph; PRUint32 nChars; PRUint32 nGlyphs; Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { } }; nsresult -gfxGraphiteShaper::SetGlyphsFromSegment(gfxTextRun *aTextRun, - PRUint32 aRunStart, - PRUint32 aRunLength, +gfxGraphiteShaper::SetGlyphsFromSegment(gfxShapedWord *aShapedWord, gr_segment *aSegment) { - PRInt32 dev2appUnits = aTextRun->GetAppUnitsPerDevUnit(); - bool rtl = aTextRun->IsRightToLeft(); + PRInt32 dev2appUnits = aShapedWord->AppUnitsPerDevUnit(); + bool rtl = aShapedWord->IsRightToLeft(); PRUint32 glyphCount = gr_seg_n_slots(aSegment); // identify clusters; graphite may have reordered/expanded/ligated glyphs. nsAutoTArray<Cluster,SMALL_GLYPH_RUN> clusters; nsAutoTArray<PRUint16,SMALL_GLYPH_RUN> gids; nsAutoTArray<float,SMALL_GLYPH_RUN> xLocs; nsAutoTArray<float,SMALL_GLYPH_RUN> yLocs; - if (!clusters.SetLength(aRunLength) || + if (!clusters.SetLength(aShapedWord->Length()) || !gids.SetLength(glyphCount) || !xLocs.SetLength(glyphCount) || !yLocs.SetLength(glyphCount)) { return NS_ERROR_OUT_OF_MEMORY; } // walk through the glyph slots and check which original character @@ -312,27 +295,27 @@ gfxGraphiteShaper::SetGlyphsFromSegment( --cIndex; } // if there's a gap between the current cluster's base character and // this glyph's, extend the cluster to include the intervening chars if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars && before >= clusters[cIndex].baseChar + clusters[cIndex].nChars) { - NS_ASSERTION(cIndex < aRunLength - 1, "cIndex at end of run"); + NS_ASSERTION(cIndex < aShapedWord->Length() - 1, "cIndex at end of word"); Cluster& c = clusters[cIndex + 1]; c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars; c.nChars = before - c.baseChar; c.baseGlyph = gIndex; c.nGlyphs = 0; ++cIndex; } // increment cluster's glyph count to include current slot - NS_ASSERTION(cIndex < aRunLength, "cIndex beyond valid run length"); + NS_ASSERTION(cIndex < aShapedWord->Length(), "cIndex beyond word length"); ++clusters[cIndex].nGlyphs; // extend cluster if necessary to reach the glyph's "after" index if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) { clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar; } } @@ -353,65 +336,64 @@ gfxGraphiteShaper::SetGlyphsFromSegment( } else { adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph]; } } // Check for default-ignorable char that didn't get filtered, combined, // etc by the shaping process, and skip it. PRUint32 offs = gr_cinfo_base(gr_seg_cinfo(aSegment, c.baseChar)); - NS_ASSERTION(offs >= c.baseChar && offs < aRunLength, + NS_ASSERTION(offs >= c.baseChar && offs < aShapedWord->Length(), "unexpected offset"); if (c.nGlyphs == 1 && c.nChars == 1 && - aTextRun->FilterIfIgnorable(aRunStart + offs)) + aShapedWord->FilterIfIgnorable(offs)) { continue; } PRUint32 appAdvance = adv * dev2appUnits; if (c.nGlyphs == 1 && - gfxTextRun::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) && - gfxTextRun::CompressedGlyph::IsSimpleAdvance(appAdvance) && + gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) && + gfxShapedWord::CompressedGlyph::IsSimpleAdvance(appAdvance) && yLocs[c.baseGlyph] == 0) { - gfxTextRun::CompressedGlyph g; - aTextRun->SetSimpleGlyph(aRunStart + offs, - g.SetSimpleGlyph(appAdvance, - gids[c.baseGlyph])); + gfxShapedWord::CompressedGlyph g; + aShapedWord->SetSimpleGlyph(offs, + g.SetSimpleGlyph(appAdvance, + gids[c.baseGlyph])); } else { // not a one-to-one mapping with simple metrics: use DetailedGlyph - nsAutoTArray<gfxTextRun::DetailedGlyph,8> details; + nsAutoTArray<gfxShapedWord::DetailedGlyph,8> details; float clusterLoc; for (PRUint32 j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) { - gfxTextRun::DetailedGlyph* d = details.AppendElement(); + gfxShapedWord::DetailedGlyph* d = details.AppendElement(); d->mGlyphID = gids[j]; d->mYOffset = -yLocs[j] * dev2appUnits; if (j == c.baseGlyph) { d->mXOffset = 0; d->mAdvance = appAdvance; clusterLoc = xLocs[j]; } else { d->mXOffset = (xLocs[j] - clusterLoc - adv) * dev2appUnits; d->mAdvance = 0; } } - gfxTextRun::CompressedGlyph g; - g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs), + gfxShapedWord::CompressedGlyph g; + g.SetComplex(aShapedWord->IsClusterStart(offs), true, details.Length()); - aTextRun->SetGlyphs(aRunStart + offs, g, details.Elements()); + aShapedWord->SetGlyphs(offs, g, details.Elements()); } for (PRUint32 j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) { offs = gr_cinfo_base(gr_seg_cinfo(aSegment, j)); - NS_ASSERTION(offs >= j && offs < aRunLength, + NS_ASSERTION(offs >= j && offs < aShapedWord->Length(), "unexpected offset"); - gfxTextRun::CompressedGlyph g; - g.SetComplex(aTextRun->IsClusterStart(aRunStart + offs), - false, 0); - aTextRun->SetGlyphs(aRunStart + offs, g, nsnull); + gfxShapedWord::CompressedGlyph g; + g.SetComplex(aShapedWord->IsClusterStart(offs), false, 0); + aShapedWord->SetGlyphs(offs, g, nsnull); } } return NS_OK; } // for language tag validation - include list of tags from the IANA registry #include "gfxLanguageTagList.cpp"
--- a/gfx/thebes/gfxGraphiteShaper.h +++ b/gfx/thebes/gfxGraphiteShaper.h @@ -47,22 +47,19 @@ class gr_face; class gr_font; class gr_segment; class gfxGraphiteShaper : public gfxFontShaper { public: gfxGraphiteShaper(gfxFont *aFont); virtual ~gfxGraphiteShaper(); - virtual bool InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript); + virtual bool ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText); const void* GetTable(PRUint32 aTag, size_t *aLength); static void Shutdown(); struct CallbackData { gfxFont *mFont; gfxGraphiteShaper *mShaper; @@ -71,19 +68,17 @@ public: struct TableRec { hb_blob_t *mBlob; const void *mData; PRUint32 mLength; }; protected: - nsresult SetGlyphsFromSegment(gfxTextRun *aTextRun, - PRUint32 aRunStart, - PRUint32 aRunLength, + nsresult SetGlyphsFromSegment(gfxShapedWord *aShapedWord, gr_segment *aSegment); gr_face *mGrFace; gr_font *mGrFont; CallbackData mCallbackData; nsDataHashtable<nsUint32HashKey,TableRec> mTables;
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp +++ b/gfx/thebes/gfxHarfBuzzShaper.cpp @@ -708,22 +708,19 @@ HBGetEastAsianWidth(hb_codepoint_t aCh) /* * gfxFontShaper override to initialize the text run using HarfBuzz */ static hb_font_funcs_t * sHBFontFuncs = nsnull; static hb_unicode_funcs_t * sHBUnicodeFuncs = nsnull; bool -gfxHarfBuzzShaper::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const PRUnichar *aString, - PRUint32 aRunStart, - PRUint32 aRunLength, - PRInt32 aRunScript) +gfxHarfBuzzShaper::ShapeWord(gfxContext *aContext, + gfxShapedWord *aShapedWord, + const PRUnichar *aText) { // some font back-ends require this in order to get proper hinted metrics mFont->SetupCairoFont(aContext); if (!mHBFace) { mUseFontGlyphWidths = mFont->ProvidesGlyphWidths(); @@ -813,92 +810,87 @@ gfxHarfBuzzShaper::InitTextRun(gfxContex FontCallbackData fcd(this, aContext); hb_font_t *font = hb_font_create(); hb_font_set_funcs(font, sHBFontFuncs, nsnull, &fcd); hb_font_set_ppem(font, mFont->GetAdjustedSize(), mFont->GetAdjustedSize()); PRUint32 scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point hb_font_set_scale(font, scale, scale); - // aRunStart and aRunLength define the section of the textRun and of - // aString that is to be drawn with this particular font - - bool disableLigatures = - (aTextRun->GetFlags() & - gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; - nsAutoTArray<hb_feature_t,20> features; // Ligature features are enabled by default in the generic shaper, // so we explicitly turn them off if necessary (for letter-spacing) - if (disableLigatures) { + if (aShapedWord->DisableLigatures()) { hb_feature_t ligaOff = { HB_TAG('l','i','g','a'), 0, 0, UINT_MAX }; hb_feature_t cligOff = { HB_TAG('c','l','i','g'), 0, 0, UINT_MAX }; features.AppendElement(ligaOff); features.AppendElement(cligOff); } // css features need to be merged with the existing ones, if any - const gfxFontStyle *style = aTextRun->GetFontGroup()->GetStyle(); + gfxFontEntry *entry = mFont->GetFontEntry(); + const gfxFontStyle *style = mFont->GetStyle(); const nsTArray<gfxFontFeature> *cssFeatures = &style->featureSettings; if (cssFeatures->IsEmpty()) { - cssFeatures = &mFont->GetFontEntry()->mFeatureSettings; + cssFeatures = &entry->mFeatureSettings; } for (PRUint32 i = 0; i < cssFeatures->Length(); ++i) { PRUint32 j; for (j = 0; j < features.Length(); ++j) { if (cssFeatures->ElementAt(i).mTag == features[j].tag) { features[j].value = cssFeatures->ElementAt(i).mValue; break; } } if (j == features.Length()) { const gfxFontFeature& f = cssFeatures->ElementAt(i); hb_feature_t hbf = { f.mTag, f.mValue, 0, UINT_MAX }; features.AppendElement(hbf); } } - hb_buffer_t *buffer = hb_buffer_create(aRunLength); + bool isRightToLeft = aShapedWord->IsRightToLeft(); + hb_buffer_t *buffer = hb_buffer_create(aShapedWord->Length()); hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs); - hb_buffer_set_direction(buffer, - aTextRun->IsRightToLeft() ? - HB_DIRECTION_RTL : HB_DIRECTION_LTR); + hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL : + HB_DIRECTION_LTR); // For unresolved "common" or "inherited" runs, default to Latin for now. // (Should we somehow use the language or locale to try and infer // a better default?) hb_buffer_set_script(buffer, - aRunScript <= HB_SCRIPT_INHERITED ? HB_SCRIPT_LATIN - : hb_script_t(aRunScript)); + aShapedWord->Script() <= HB_SCRIPT_INHERITED ? + HB_SCRIPT_LATIN : + hb_script_t(aShapedWord->Script())); hb_language_t language; if (style->languageOverride) { language = hb_ot_tag_to_language(style->languageOverride); - } else if (mFont->GetFontEntry()->mLanguageOverride) { - language = - hb_ot_tag_to_language(mFont->GetFontEntry()->mLanguageOverride); + } else if (entry->mLanguageOverride) { + language = hb_ot_tag_to_language(entry->mLanguageOverride); } else { nsCString langString; style->language->ToUTF8String(langString); language = hb_language_from_string(langString.get()); } hb_buffer_set_language(buffer, language); - hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aString + aRunStart), - aRunLength, 0, aRunLength); + PRUint32 length = aShapedWord->Length(); + hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t*>(aText), + length, 0, length); hb_shape(font, mHBFace, buffer, features.Elements(), features.Length()); - if (aTextRun->IsRightToLeft()) { + if (isRightToLeft) { hb_buffer_reverse(buffer); } - nsresult rv = - SetGlyphsFromRun(aContext, aTextRun, buffer, aRunStart, aRunLength); - NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into textrun"); + nsresult rv = SetGlyphsFromRun(aContext, aShapedWord, buffer); + + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into gfxShapedWord&