Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

627 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. /* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data.
  16. It's needed because although win32 will happily load a TTF file from in-memory data, it won't
  17. tell you the name of the damned font that it just loaded.. and in order to actually use the font,
  18. you need to know its name!! Anyway, this awful hack seems to work for most fonts.
  19. */
  20. namespace TTFNameExtractor
  21. {
  22. struct OffsetTable
  23. {
  24. uint32 version;
  25. uint16 numTables, searchRange, entrySelector, rangeShift;
  26. };
  27. struct TableDirectory
  28. {
  29. char tag[4];
  30. uint32 checkSum, offset, length;
  31. };
  32. struct NamingTable
  33. {
  34. uint16 formatSelector;
  35. uint16 numberOfNameRecords;
  36. uint16 offsetStartOfStringStorage;
  37. };
  38. struct NameRecord
  39. {
  40. uint16 platformID, encodingID, languageID;
  41. uint16 nameID, stringLength, offsetFromStorageArea;
  42. };
  43. static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord,
  44. const int64 directoryOffset, const int64 offsetOfStringStorage)
  45. {
  46. String result;
  47. auto oldPos = input.getPosition();
  48. input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea));
  49. auto stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength);
  50. auto platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID);
  51. if (platformID == 0 || platformID == 3)
  52. {
  53. auto numChars = stringLength / 2 + 1;
  54. HeapBlock<uint16> buffer;
  55. buffer.calloc (numChars + 1);
  56. input.read (buffer, stringLength);
  57. for (int i = 0; i < numChars; ++i)
  58. buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]);
  59. static_assert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16), "Sanity check UTF-16 type");
  60. result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData());
  61. }
  62. else
  63. {
  64. HeapBlock<char> buffer;
  65. buffer.calloc (stringLength + 1);
  66. input.read (buffer, stringLength);
  67. result = CharPointer_UTF8 (buffer.getData());
  68. }
  69. input.setPosition (oldPos);
  70. return result;
  71. }
  72. static String parseNameTable (MemoryInputStream& input, int64 directoryOffset)
  73. {
  74. input.setPosition (directoryOffset);
  75. NamingTable namingTable = {};
  76. input.read (&namingTable, sizeof (namingTable));
  77. for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i)
  78. {
  79. NameRecord nameRecord = {};
  80. input.read (&nameRecord, sizeof (nameRecord));
  81. if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4)
  82. {
  83. const String result (parseNameRecord (input, nameRecord, directoryOffset,
  84. ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage)));
  85. if (result.isNotEmpty())
  86. return result;
  87. }
  88. }
  89. return {};
  90. }
  91. static String getTypefaceNameFromFile (MemoryInputStream& input)
  92. {
  93. OffsetTable offsetTable = {};
  94. input.read (&offsetTable, sizeof (offsetTable));
  95. for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i)
  96. {
  97. TableDirectory tableDirectory;
  98. zerostruct (tableDirectory);
  99. input.read (&tableDirectory, sizeof (tableDirectory));
  100. if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name"))
  101. return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset));
  102. }
  103. return {};
  104. }
  105. }
  106. namespace FontEnumerators
  107. {
  108. static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
  109. {
  110. if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0)
  111. {
  112. const String fontName (lpelfe->elfLogFont.lfFaceName);
  113. ((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@"));
  114. }
  115. return 1;
  116. }
  117. static int CALLBACK fontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
  118. {
  119. if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0)
  120. {
  121. LOGFONTW lf = {};
  122. lf.lfWeight = FW_DONTCARE;
  123. lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
  124. lf.lfQuality = DEFAULT_QUALITY;
  125. lf.lfCharSet = DEFAULT_CHARSET;
  126. lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
  127. lf.lfPitchAndFamily = FF_DONTCARE;
  128. const String fontName (lpelfe->elfLogFont.lfFaceName);
  129. fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName));
  130. auto dc = CreateCompatibleDC (nullptr);
  131. EnumFontFamiliesEx (dc, &lf, (FONTENUMPROCW) &fontEnum2, lParam, 0);
  132. DeleteDC (dc);
  133. }
  134. return 1;
  135. }
  136. }
  137. StringArray Font::findAllTypefaceNames()
  138. {
  139. StringArray results;
  140. #if JUCE_USE_DIRECTWRITE
  141. SharedResourcePointer<Direct2DFactories> factories;
  142. if (factories->systemFonts != nullptr)
  143. {
  144. ComSmartPtr<IDWriteFontFamily> fontFamily;
  145. uint32 fontFamilyCount = 0;
  146. fontFamilyCount = factories->systemFonts->GetFontFamilyCount();
  147. for (uint32 i = 0; i < fontFamilyCount; ++i)
  148. {
  149. auto hr = factories->systemFonts->GetFontFamily (i, fontFamily.resetAndGetPointerAddress());
  150. if (SUCCEEDED (hr))
  151. results.addIfNotAlreadyThere (getFontFamilyName (fontFamily));
  152. }
  153. }
  154. else
  155. #endif
  156. {
  157. auto dc = CreateCompatibleDC (nullptr);
  158. {
  159. LOGFONTW lf = {};
  160. lf.lfWeight = FW_DONTCARE;
  161. lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
  162. lf.lfQuality = DEFAULT_QUALITY;
  163. lf.lfCharSet = DEFAULT_CHARSET;
  164. lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
  165. lf.lfPitchAndFamily = FF_DONTCARE;
  166. EnumFontFamiliesEx (dc, &lf,
  167. (FONTENUMPROCW) &FontEnumerators::fontEnum1,
  168. (LPARAM) &results, 0);
  169. }
  170. DeleteDC (dc);
  171. }
  172. results.sort (true);
  173. return results;
  174. }
  175. StringArray Font::findAllTypefaceStyles (const String& family)
  176. {
  177. if (FontStyleHelpers::isPlaceholderFamilyName (family))
  178. return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
  179. StringArray results;
  180. #if JUCE_USE_DIRECTWRITE
  181. SharedResourcePointer<Direct2DFactories> factories;
  182. if (factories->systemFonts != nullptr)
  183. {
  184. BOOL fontFound = false;
  185. uint32 fontIndex = 0;
  186. auto hr = factories->systemFonts->FindFamilyName (family.toWideCharPointer(), &fontIndex, &fontFound);
  187. if (! fontFound)
  188. fontIndex = 0;
  189. // Get the font family using the search results
  190. // Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
  191. ComSmartPtr<IDWriteFontFamily> fontFamily;
  192. hr = factories->systemFonts->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
  193. // Get the font faces
  194. ComSmartPtr<IDWriteFont> dwFont;
  195. uint32 fontFacesCount = 0;
  196. fontFacesCount = fontFamily->GetFontCount();
  197. for (uint32 i = 0; i < fontFacesCount; ++i)
  198. {
  199. hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
  200. // Ignore any algorithmically generated bold and oblique styles..
  201. if (dwFont->GetSimulations() == DWRITE_FONT_SIMULATIONS_NONE)
  202. results.addIfNotAlreadyThere (getFontFaceName (dwFont));
  203. }
  204. }
  205. else
  206. #endif
  207. {
  208. results.add ("Regular");
  209. results.add ("Italic");
  210. results.add ("Bold");
  211. results.add ("Bold Italic");
  212. }
  213. return results;
  214. }
  215. extern bool juce_isRunningInWine();
  216. struct DefaultFontNames
  217. {
  218. DefaultFontNames()
  219. {
  220. if (juce_isRunningInWine())
  221. {
  222. // If we're running in Wine, then use fonts that might be available on Linux..
  223. defaultSans = "Bitstream Vera Sans";
  224. defaultSerif = "Bitstream Vera Serif";
  225. defaultFixed = "Bitstream Vera Sans Mono";
  226. }
  227. else
  228. {
  229. defaultSans = "Verdana";
  230. defaultSerif = "Times New Roman";
  231. defaultFixed = "Lucida Console";
  232. defaultFallback = "Tahoma"; // (contains plenty of unicode characters)
  233. }
  234. }
  235. String defaultSans, defaultSerif, defaultFixed, defaultFallback;
  236. };
  237. Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
  238. {
  239. static DefaultFontNames defaultNames;
  240. Font newFont (font);
  241. auto& faceName = font.getTypefaceName();
  242. if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
  243. else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
  244. else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
  245. if (font.getTypefaceStyle() == getDefaultStyle())
  246. newFont.setTypefaceStyle ("Regular");
  247. return Typeface::createSystemTypefaceFor (newFont);
  248. }
  249. //==============================================================================
  250. class WindowsTypeface : public Typeface
  251. {
  252. public:
  253. WindowsTypeface (const Font& font) : Typeface (font.getTypefaceName(),
  254. font.getTypefaceStyle())
  255. {
  256. loadFont();
  257. }
  258. WindowsTypeface (const void* data, size_t dataSize)
  259. : Typeface (String(), String())
  260. {
  261. DWORD numInstalled = 0;
  262. memoryFont = AddFontMemResourceEx (const_cast<void*> (data), (DWORD) dataSize,
  263. nullptr, &numInstalled);
  264. MemoryInputStream m (data, dataSize, false);
  265. name = TTFNameExtractor::getTypefaceNameFromFile (m);
  266. loadFont();
  267. }
  268. ~WindowsTypeface()
  269. {
  270. SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker
  271. DeleteDC (dc);
  272. if (fontH != nullptr)
  273. DeleteObject (fontH);
  274. if (memoryFont != nullptr)
  275. RemoveFontMemResourceEx (memoryFont);
  276. }
  277. float getAscent() const { return ascent; }
  278. float getDescent() const { return 1.0f - ascent; }
  279. float getHeightToPointsFactor() const { return heightToPointsFactor; }
  280. float getStringWidth (const String& text)
  281. {
  282. auto utf16 = text.toUTF16();
  283. auto numChars = utf16.length();
  284. HeapBlock<uint16> results (numChars);
  285. float x = 0;
  286. if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
  287. GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
  288. {
  289. for (size_t i = 0; i < numChars; ++i)
  290. x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
  291. }
  292. return x;
  293. }
  294. void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
  295. {
  296. auto utf16 = text.toUTF16();
  297. auto numChars = utf16.length();
  298. HeapBlock<uint16> results (numChars);
  299. float x = 0;
  300. if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
  301. GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
  302. {
  303. resultGlyphs.ensureStorageAllocated ((int) numChars);
  304. xOffsets.ensureStorageAllocated ((int) numChars + 1);
  305. for (size_t i = 0; i < numChars; ++i)
  306. {
  307. resultGlyphs.add (results[i]);
  308. xOffsets.add (x);
  309. x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
  310. }
  311. }
  312. xOffsets.add (x);
  313. }
  314. bool getOutlineForGlyph (int glyphNumber, Path& glyphPath)
  315. {
  316. if (glyphNumber < 0)
  317. glyphNumber = defaultGlyph;
  318. GLYPHMETRICS gm;
  319. // (although GetGlyphOutline returns a DWORD, it may be -1 on failure, so treat it as signed int..)
  320. auto bufSize = (int) GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX,
  321. &gm, 0, nullptr, &identityMatrix);
  322. if (bufSize > 0)
  323. {
  324. HeapBlock<char> data (bufSize);
  325. GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm,
  326. (DWORD) bufSize, data, &identityMatrix);
  327. auto pheader = reinterpret_cast<const TTPOLYGONHEADER*> (data.getData());
  328. auto scaleX = 1.0f / tm.tmHeight;
  329. auto scaleY = -scaleX;
  330. while ((char*) pheader < data + bufSize)
  331. {
  332. glyphPath.startNewSubPath (scaleX * pheader->pfxStart.x.value,
  333. scaleY * pheader->pfxStart.y.value);
  334. auto curve = (const TTPOLYCURVE*) ((const char*) pheader + sizeof (TTPOLYGONHEADER));
  335. auto curveEnd = ((const char*) pheader) + pheader->cb;
  336. while ((const char*) curve < curveEnd)
  337. {
  338. if (curve->wType == TT_PRIM_LINE)
  339. {
  340. for (int i = 0; i < curve->cpfx; ++i)
  341. glyphPath.lineTo (scaleX * curve->apfx[i].x.value,
  342. scaleY * curve->apfx[i].y.value);
  343. }
  344. else if (curve->wType == TT_PRIM_QSPLINE)
  345. {
  346. for (int i = 0; i < curve->cpfx - 1; ++i)
  347. {
  348. auto x2 = scaleX * curve->apfx[i].x.value;
  349. auto y2 = scaleY * curve->apfx[i].y.value;
  350. auto x3 = scaleX * curve->apfx[i + 1].x.value;
  351. auto y3 = scaleY * curve->apfx[i + 1].y.value;
  352. if (i < curve->cpfx - 2)
  353. {
  354. x3 = 0.5f * (x2 + x3);
  355. y3 = 0.5f * (y2 + y3);
  356. }
  357. glyphPath.quadraticTo (x2, y2, x3, y3);
  358. }
  359. }
  360. curve = (const TTPOLYCURVE*) &(curve->apfx [curve->cpfx]);
  361. }
  362. pheader = (const TTPOLYGONHEADER*) curve;
  363. glyphPath.closeSubPath();
  364. }
  365. }
  366. return true;
  367. }
  368. private:
  369. static const MAT2 identityMatrix;
  370. HFONT fontH = {};
  371. HGDIOBJ previousFontH = {};
  372. HDC dc { CreateCompatibleDC (nullptr) };
  373. TEXTMETRIC tm;
  374. HANDLE memoryFont = {};
  375. float ascent = 1.0f, heightToPointsFactor = 1.0f;
  376. int defaultGlyph = -1, heightInPoints = 0;
  377. std::unordered_map<uint64, float> kerningPairs;
  378. static uint64 kerningPairIndex (int glyph1, int glyph2)
  379. {
  380. return (((uint64) (uint32) glyph1) << 32) | (uint64) (uint32) glyph2;
  381. }
  382. void loadFont()
  383. {
  384. SetMapperFlags (dc, 0);
  385. SetMapMode (dc, MM_TEXT);
  386. LOGFONTW lf = {};
  387. lf.lfCharSet = DEFAULT_CHARSET;
  388. lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
  389. lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
  390. lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
  391. lf.lfQuality = PROOF_QUALITY;
  392. lf.lfItalic = (BYTE) (style.contains ("Italic") ? TRUE : FALSE);
  393. lf.lfWeight = style.contains ("Bold") ? FW_BOLD : FW_NORMAL;
  394. lf.lfHeight = -256;
  395. name.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName));
  396. auto standardSizedFont = CreateFontIndirect (&lf);
  397. if (standardSizedFont != nullptr)
  398. {
  399. if ((previousFontH = SelectObject (dc, standardSizedFont)) != nullptr)
  400. {
  401. fontH = standardSizedFont;
  402. OUTLINETEXTMETRIC otm;
  403. if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0)
  404. {
  405. heightInPoints = (int) otm.otmEMSquare;
  406. lf.lfHeight = -heightInPoints;
  407. fontH = CreateFontIndirect (&lf);
  408. SelectObject (dc, fontH);
  409. DeleteObject (standardSizedFont);
  410. }
  411. }
  412. }
  413. if (GetTextMetrics (dc, &tm))
  414. {
  415. auto dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f;
  416. heightToPointsFactor = (dpi / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight;
  417. ascent = tm.tmAscent / (float) tm.tmHeight;
  418. std::unordered_map<int, int> glyphsForChars;
  419. defaultGlyph = getGlyphForChar (dc, glyphsForChars, tm.tmDefaultChar);
  420. createKerningPairs (dc, glyphsForChars, (float) tm.tmHeight);
  421. }
  422. }
  423. void createKerningPairs (HDC hdc, std::unordered_map<int, int>& glyphsForChars, float height)
  424. {
  425. HeapBlock<KERNINGPAIR> rawKerning;
  426. auto numKPs = GetKerningPairs (hdc, 0, nullptr);
  427. rawKerning.calloc (numKPs);
  428. GetKerningPairs (hdc, numKPs, rawKerning);
  429. std::unordered_map<int, int> widthsForGlyphs;
  430. for (DWORD i = 0; i < numKPs; ++i)
  431. {
  432. auto glyph1 = getGlyphForChar (hdc, glyphsForChars, rawKerning[i].wFirst);
  433. auto glyph2 = getGlyphForChar (hdc, glyphsForChars, rawKerning[i].wSecond);
  434. auto standardWidth = getGlyphWidth (hdc, widthsForGlyphs, glyph1);
  435. kerningPairs[kerningPairIndex (glyph1, glyph2)] = (standardWidth + rawKerning[i].iKernAmount) / height;
  436. kerningPairs[kerningPairIndex (glyph1, -1)] = standardWidth / height;
  437. }
  438. }
  439. static int getGlyphForChar (HDC dc, std::unordered_map<int, int>& cache, juce_wchar character)
  440. {
  441. auto existing = cache.find ((int) character);
  442. if (existing != cache.end())
  443. return existing->second;
  444. const WCHAR charToTest[] = { (WCHAR) character, 0 };
  445. WORD index = 0;
  446. if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR
  447. || index == 0xffff)
  448. return -1;
  449. cache[(int) character] = index;
  450. return index;
  451. }
  452. static int getGlyphWidth (HDC dc, std::unordered_map<int, int>& cache, int glyphNumber)
  453. {
  454. auto existing = cache.find (glyphNumber);
  455. if (existing != cache.end())
  456. return existing->second;
  457. auto width = getGlyphWidth (dc, glyphNumber);
  458. cache[glyphNumber] = width;
  459. return width;
  460. }
  461. static int getGlyphWidth (HDC dc, int glyphNumber)
  462. {
  463. GLYPHMETRICS gm;
  464. gm.gmCellIncX = 0;
  465. GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, &identityMatrix);
  466. return gm.gmCellIncX;
  467. }
  468. float getKerning (HDC hdc, int glyph1, int glyph2)
  469. {
  470. auto pair = kerningPairs.find (kerningPairIndex (glyph1, glyph2));
  471. if (pair != kerningPairs.end())
  472. return pair->second;
  473. auto single = kerningPairs.find (kerningPairIndex (glyph1, -1));
  474. if (single != kerningPairs.end())
  475. return single->second;
  476. auto width = getGlyphWidth (hdc, glyph1) / (float) tm.tmHeight;
  477. kerningPairs[kerningPairIndex (glyph1, -1)] = width;
  478. return width;
  479. }
  480. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTypeface)
  481. };
  482. const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } };
  483. Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
  484. {
  485. #if JUCE_USE_DIRECTWRITE
  486. SharedResourcePointer<Direct2DFactories> factories;
  487. if (factories->systemFonts != nullptr)
  488. {
  489. std::unique_ptr<WindowsDirectWriteTypeface> wtf (new WindowsDirectWriteTypeface (font, factories->systemFonts));
  490. if (wtf->loadedOk())
  491. return wtf.release();
  492. }
  493. #endif
  494. return new WindowsTypeface (font);
  495. }
  496. Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize)
  497. {
  498. return new WindowsTypeface (data, dataSize);
  499. }
  500. void Typeface::scanFolderForFonts (const File&)
  501. {
  502. jassertfalse; // not implemented on this platform
  503. }
  504. } // namespace juce