Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GdiGraphics and ThemeManager API extensions and fixes #147

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

razielanarki
Copy link
Contributor

@razielanarki razielanarki commented Sep 16, 2021

Hi!

This "monstre" PR mainly extends the capabilites of the GdiGraphics API and the ThemeManager API with the following callbacks (and some other things listed after):

GdiGraphics

  • GetX counterparts for SetInterpolationMode, SetSmoothingMode and SetTextRenderingHint
  • new GetX / SetX functions for accessing TextContrast, CompositingMode, CompositingQuality and PixelOffsetMode on Gdiplus::Graphics objects
  • functions for getting/setting/resetting a clipping rectangle
  • functions accessing and working with the world transformation matrix (get/set/reset, translate, rotate, scale, multiply, transformpoint/untransformpoint, transformrect/untransformrect
  • GdiDrawText respects the clipping and transformation set

The changes were made to enable easier implementing of custom UI elements with scrolling contents (using clipping + translate transforms), and other functions were introduced for completeness (getX + setX, and rotate + scale + multiply)

ThemeManager

  • DrawThemeBackground respects the clipping and transformation set, and the clipping rectangle specified by the clip_X parameters is fixed
  • a DrawThemeText function for drawing text using the font and color defined in the theme for the partId and stateId selected, based on the implementation of GdiGraphics::GdiDrawText
  • functions for accessing theme part properties: GetThemePropertyOrigin, GetThemeMetric, GetThemeBool, GetThemeInt, GetThemeColour, GetThemeMargins, GetThemePartSize, GetThemeEnumValue, GetThemeBackgroundContentRect
  • functions for accessing theme "system" properties: GetThemeSysInt, GetThemeSysSize, GetThemeSysColour
  • functions for accessing the font defined in the the properties, with one variant returning a GdiFont object, and one variant returning the fontName, fontSize and fontStyle arguments as an array: GetThemeFont, GetThemeFontArgs, GetThemeSysFont, GetThemeSysFontArgs

The changes were made to extend the functionality when implementing themed controls, developers now can use the fonts and colors defined in the theme, and set control sizes and margins according to the theme.

Other changes and fixes

  • the GdiGraphics and ThemeManager classes have been largely refactored, mainly the order of the functions changed, and topically grouped together.
    Also, some variable and parameter names were changed. The types and order of variables and parameter WERE NOT changed.
  • the foo_spider_monkey_panel.js file has been updated with the new API (not sure about how to update the html docs properly)
  • CHANGED: the error display font to the default system font ("Tahoma" was hardcoded)
  • FIXED: the GdiFont::Constructor: previously when creating a GdiFont instance from an on_paint callback, a GdiDrawText call following in the same callback didn't draw anything (and didn't throw an error).
    Now the text is drawn correctly.

Important changes

  • ADDED: and advanced config checkbox to suppress the JS error popup (but still log the exception to the console), saves a few clicks while developing scripts
    (I will submit a small PR to fb2k_utils too)
    edit: the PR is at: Add an option to enable suppression of the error popup  fb2k_utils#1
  • CHANGED: removed the unary - from the fontSize in the aforementioned GdiFont constructor.
    THIS MAY BREAK some existing scripts in a minor way, but for me it solved quite a lot of headaches while developing scripts.
    So the default fontSize is now in PT units (as everywhere else in windows), and specifying a negative font size equals PX units.

In conclusion

I suspect that this won't be a simple merge, and it will require some discussion and amendments on my part before being accepted to the main repository, and I'm very open to comments and suggestions on making this extensions a part of SMP.

(It is also worth noting that I'm developing a "naive" approach JS GUI library for drawing themed controls.)

@TheQwertiest
Copy link
Owner

TheQwertiest commented Sep 16, 2021

Thanks for the PR!
Regretfully, I don't think I'll be able to look into it any time soon, since I need to fix all the issues in the master for the point release first...

One thing though, alphabetic sorting of the methods is a conscious design choice, so, please, don't reorder methods :)

…nd added GdiGraphics support for nested containers.
@razielanarki
Copy link
Contributor Author

razielanarki commented Sep 17, 2021

One thing though, alphabetic sorting of the methods is a conscious design choice, so, please, don't reorder methods :)

Ouch! slaps forehead I should've seen that pattern, …instead I just assumed the methods were in implementation order.
I've restored the alphabetical order of methods in both places.

And while at it, I've also reverted the change regarding the font size in GdiFont::Constructor but made the fontSize parameter a signed int32_t so devs can select fonts by cell height (fontSize<0) and character height (fontSize>0) too (I was wrong before it's not PT vs PX, heh, however this behavior still inverts the way it's documented in the win32 API), and fixed the default value of new advanced config variable to true so the default behavior is the same as before (i.e. the exception popup is shown).

And finally, added functions for saving and restoring the graphics object's state, and support for nested container, which could come in handy when drawing hierarchical ui-s ( https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-using-graphics-containers-use ), and added support for the GetDpiX and GetDpiY methods provided by the Gdiplus::Graphics object.

@TheQwertiest
Copy link
Owner

Have yet to check the main stuff, but ReportErrorWithPopup change makes no sense. I mean why call ReportErrorWithPopup at all if you don't need the popup? =)

@marc2k3
Copy link
Contributor

marc2k3 commented Sep 25, 2021

I know less than nothing about any of it but I can tell you the DT_CALCRECT stuff copied from GdiDrawText to DrawThemeText flat out doesn't work (I tested the appveyor build). You probably want to use GetThemeTextExtent.

https://docs.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-getthemetextextent

Also, the font weight is heavier than expected.

Exposing GetThemeColour is a great idea though - I already stole that.

@razielanarki
Copy link
Contributor Author

Have yet to check the main stuff, but ReportErrorWithPopup change makes no sense. I mean why call ReportErrorWithPopup at all if you don't need the popup? =)

@TheQwertiest:
hey, thanks for taking the time to review!
so the idea was, that when developing scripts, i usually have a floating foobar console open (via a cui button bound to 'view\console'), that already displays the errors / devlogs for me, and the panel that "crashed" is already clearly and distinctively marked (from the simple "music-fan" end users pov, too), so the error popup is (as of now) just an extra confirmation step before the user (or a dev) can get back to doint whatever he was doing. so by default i hid it (hence the 'showPopup` arg,) however that changed the original behavior, so i flipped the config var to default to true, to conserve the behavior (and i think that i fund the right place where only the uncaught jsapi exceptions utilize this, as from my experience, the other errors thrown really need more attention / harder to recover, than a panel reload after correcting scripts/configs/ect)

@marc2k3:
Thanks to you too! and.. hmm, i did some superficial testing, as i assumed (from the docs) that DrawThemeTextEx works just like the GDI DrawText (and for me it looks like it mostly does.. so, yeah, i havent troughly tested everything like CALC_RECT and left-right alignments).
and yes, the fonts did seem "heavier" to me even before that, and actually that was one of the the things that got my deepdive into mozjs and win32 c++ started 👼
so as far as is understood the code there are 3 GDI layers at work there: from the foobar/ panel side there is the MFC/WTL (ATL) wrapped CDC, and from jsapi there's mixed calls to GDI+ and GDI functions (i kind of spent a day and a half trying out new graphics parameters (which i connected the js api too, like TextContrast and so), to try and find a way to match the font rendering to the MFC/ATL ui elements rendered by foobar. in theory they should be the same, and the only line of investigation ive left open is that maybe somewhere somehow the CreateCompatible memDC is not a "top-down" DDB so cleartype BGR/RGB component aliasing doesn't work the same... (or maybe it's a sRGB vs Linear color space alpha mismatch)
in theory they should be the same, but since the ATL layer is involved too, (and it's the most abstracted), the actual cause or likely the accidental side effect of some call made/not made is very hard to track down.

Since then I've been toying with things like patching the createhwnd() trough to javascript so custom WS_POPUP controls (like foo_osd, but scripted) could be made (with the same onpaint callback, but passing a different GdiGraphics, while maybe having to register an extra js callback for the parts of wndproc that cannot be generalized from the c++ side ), or god forbid, comctrl32 controls with native win32 api, but i havent got much mileage in this direction, other than it shoud be theoretically very possible (even more so with the utils.GetSysMetric calls, which allow one to query the virtual desktop rect, etc), but since my few weeks off work ended, and I got back to my day job i haven't got that much time to experiment.

I did however hack together something which , for now at least, i'd rather share just as an attachment here: and contains some a v0 basic ui, a half rewritten v1, some demo/test scripts, a gross hack tag normalizer (uhh, dont ask, but could be useful if i refactor it sometime), and an utility that uses ArrayBuffers to decode TRAKTOR4 tags in mp3&flacs...

Apart from that it also contains some ES6 style class definitions of the Wshell, FSO, Shell.Application, Adodb.Stream ActiveXobjects (in /lib/api/...), and some win32/gdi api related flag/enum/const ones at (/lib/gui/winapi/...), (mostly an extension of Flags.js with theme / themeprop data, and some extra enums) that could be considered useful for reference / documentation / intellisense purposes.

!foo-raz-scripts.zip

and an "obligatory" screenshot of the demos
screen

@TheQwertiest
Copy link
Owner

@razielanarki I understand your scenario, but what I mean is that ShowPopup is not the right method to have a disable_popup argument. It's like having a method Delete with argument dont_delete. I.e. if you don't need the primary effect of the call, then just don't invoke said call at all. =)

@razielanarki
Copy link
Contributor Author

razielanarki commented Nov 15, 2021

Hi again!

So i've been hacking away at the code slowly since, fixing stuff and add some more serious/useful features.

like:

Win7 and GDI compatible DirectWrite text renderer

managed to tune it almost pixel-perfercly/metrically to be GDI compatible,
except when the text contains mapped glyphs from other fonts (like Segoe Emojis),
and possibly when using bitmap fonts (like "Sytem")

currently a GdiDrawText-like and a DrawString-like API is implemented,
with a CalcWriteHeight/Width measurement call which measures the whole text,
(however flags / formats are only supported when drawing text, and its v)

additionally, if the textLayout range-foramtting api were to be exposed,
"rich-text" with multi-color/font/styls (html/lyrics/etc) could be easily rendered in one call,

supports COLR fonts, and maybe paletted SVG (haven't tested the latter),
(the code should work with DWriteCore too)

GdiFont Caching

implemented using shared_hfont/weak_hfont handles from WIL ( https://github.com/microsoft/wil ),
based on an idea seen in 'pango' ( https://gitlab.gnome.org/GNOME/pango/-/blob/main/pango/pangowin32-fontcache.c#L111 ),
with a custom std::hash<LOGFONTW> and a std::unordered_map with regular purging instead of a LRU cache

the GdiPlus font handling were factored out to the write/measure string calls that use them, and
GdiFont instead uses TEXTMETRICW internally to calculate heights/sizes of a font etc.
fonts queried from CUI/DUI are copied/duplicated in the cache, as caching internal use HFONTs didn't seem like a good idea.

so all font properties can be read/written to now, the GdiFont internally recalculates metrics,
and tries to use an already cached HFONT if available,

experimented a little with hashing/hash_combiner functions too (in gdi_font.h),
maybe could be useful elsewhere (the include path hacks/cache come to mind, but i have not looked at it yet)

(note: wil is not yet included in a submodule, in these commits, but there's more to come)

PS:

these latter two "bigger" features should be relatively easily extracted, however, this PR begins feels more and more like a feature creep 👼

hacks/tweaks/bugfixes incl, the afore mentioned code readability fixes

details : ...

(most likely i will miss some, but for completeness / discussion sake i try to list all)

common/submodules

  • merged all fixes from the main branch
  • amended the common includes (flags.js) with flags/values related to other changes
  • separated the reporterror fn to a separate call, maintaining code readabiliy :)
    ( also, hacked away some complexity, as the is_main_thread() checks/asserts are duplicated on the fb2k side before the lambda passed gets to executed)

GdiGraphics

  • renamed the Gdi call wrapper/callback fn to WrapGdiCall, as it became explicitly clear to be, and to make it more clear that it should mark places
    where GDI calls are to be mixed in to a mostly GDI+ environment, based on recommendations in this article
    https://docs.microsoft.com/en-GB/troubleshoot/windows/win32/mix-gdi-and-gdi-plus-drawing

  • removed the exceptions from roundrect arc width checks, and instead clamped them at may w/2, h/2

  • implemented DrawFocusRect / DrawEdge calls

  • removed the Get/Set compositing mode calls as it just made GDI+ cleartype rendering fail 😐

  • other Get/Set methods were aliased as JS properties with getter/setters the graphics object itself

Utils

  • added a GetDeviceCaps() call, querying the caps (ex LOGPIXELSX/Y) of the device the panel is on

ThemeManager

  • DrawThemeText actually works like GDiDrawText, but it needed bugfixing :)
  • DrawThemeText now also pre-selects the correct UI font into the DC (which would be the "System" bitmap font on a fresh DC)
  • some additional functions / checks
  • todo: maybe evaulate / cull some superflous api calls?

js_panel_window : minor refactorings/optimizations on a getting-to-know-the-code-better level

  • switched from w/h props to storing the clientRect (which is always {0,0,W,H} format)
    instead, hoping to optimize some window management code paths
    (the callbacks are half-"decoupled", they just read this prop now, instead of relying on explcity passed values)

    still keeping an eye on foo_osd like js-scriptable overlays, also looking at a https://github.com/rodrigocfd/winlamb style impl. with JS callback lambdas,

  • added a convenience feature: that double clicking a crashed panel tries to reload the script

  • relating to the former: added a HasFailed method, for checking the same checks in the paint/dblclick callbacks

  • added/simplified clip+visibility checks, just before calling the js paint handler, seems to work, and saves a few cycles (esp if the paint handler repeatedly called itself with a timeout, emulating animation 😁)

to be continued :)

 - uses a `std::default_delete` specialization
 - allows assigning "related" `std::shared_ptr<unique_gdi_handle<H>::element_type>` and
   their `::weak_type`-s from an `unique_gdi_handle<H>` while copying the
   deleter

   the latter allows to remove the dependeny n WIL in the GdiFont HFONT
   cache
@razielanarki razielanarki force-pushed the feature/gdi_graphics_and_theme_manager_api_extensions_and_fixes branch from 3ba55f3 to d904691 Compare November 21, 2021 11:58
@TT-ReBORN
Copy link

Hi @razielanarki,

I see you try to give TheQwertiest another hand and help in SMP development which is great!

I take this chance to contact you in hope you can fix some bugs/crashes in SMP when using Wine under Linux.
This is not a selfish request because this affects all users when using WilB's library and biography, the Linux fraction
would be greatful if you can fix these issues listed here:

TT-ReBORN/Georgia-ReBORN#2

If you want you can contact me in private here:
https://hydrogenaud.io/index.php?action=profile;u=139848

It would be the best to also contact WilB and work with him together to fix these issues, here is his contact:
https://hydrogenaud.io/index.php?action=profile;u=33113

You can send WilB my greetings and also from paregistrase.

I know TheQwertiest is pretty busy with other things, so I gave it a shot to contact you since I saw your pretty
motivated and enthusiastic. There aren't many good music players under Linux and it would great if Georgia-ReBORN
and other people who are using foobar + WilB's Library and Biography scripts would be crash free and fetch images from the Biography.

Thank you!

-TT

@razielanarki
Copy link
Contributor Author

Hi, thank you for the kinds words @TT-ReBORN !

Heh, i usually think this of this as just some hacking away at some mildly useful graphics related parts, while trying to get a deeper understanding of the codebase as a whole (and i mean just the smp codebase, but also reading heavily into/trying to wrap my head around related projects), and catching up on my far-left-behind-while-being-a-fullstack-webdev-guy c++ skills, which i haven't really used since,.. like the first versions of openttd... (and before that i hacked mostly in standard c -- doom/q1), so yeah, i have a lot to catch up on yet. (and i have a few pet projects/hacks in smp/foobar i still aim to cobble together)
and seeing that most of the foobar community blossomed around 15 years ago...feels kinda nostalgic at times :) but still hacking away at it is a great pastime for me for the last few months, and a great intersection of my fav hobbies, hacking and music )

all that said, i might try to look at some of the bugs you mentioned, but i cant promise anything ... windows is a complex system, with layers upon layers of legacy code some parts of which are virtually unchanged since the win3.11 days.. (because "the customers need their apps working" motto) and i can only really admire the determination of the wine/reactos guys to try to replicate it, or even better: its like changing the whole car from a ford-T to a ferrari under, leaving the dashboard and steering, and most parts of the passenger cabin intact in tiny detail, while also making everything a part of the "new". ("The Old New Thing" is a very exact phrase to describe it, and its the title for a favorite of mine developer blog from one of the guys who has seen it (windows) through all)
(long story short, i have seen/dug around in wine code, and maybe -maybe- able to help, luckily i have a spare thinkpad t420 with debian11, i might just check out if foobar starts at all :) )

and while i'm there, writing this thank you, in a very long winded post...:),

i wanted to ask @TheQwertiest , if i may: do you know any online mirrors, or such, for the (sadly taken down) MDN version of spidermonkey docs (save building it for myself ...but how..must be some docs/notes somewhere)? (spidermokey.dev is just -- doesnt cut it)
the latest/most complete i managed to find is from the spidermonkey52 times ( http://udn.realityripple.com/docs/Mozilla/Projects/SpiderMonkey ), and i can see quite a few things changed since then even in sm68 :) thanks !

and again thanks @TT-ReBORN ! i might make myself useful! (makes zoidberg noises) :)

@TT-ReBORN
Copy link

Hi @razielanarki, thanks for your answer and hope in fixing the bugs/crashes =).

I wanted to release Georgia-ReBORN crash free for the first public release, these issues are old and already documented multiple times in the HydrogenAudio forums ( WilB's biography and library thread ) by Linux users...

If this does help you, here is an answer from WilB about fetching images in his biography script:


"RE: downloading images

I did put some console traces in the web.js I sent you to check this. Below is what you sent me. You can see that artist photo links were correctly extracted. However, presumably they didn't save. The save process streams the image with windows cscript that's run through ActiveXObject('WScript.Shell'), so I expect it's failing there: I couldn't see anything I could change in that. I also had a quick look for an alternative method to save images but drew a blank, unless you can come up with something.

In due course SMP may get updated with modern web-look-up methods that don't use ActiveXObject & so image saving may become much easier."

He also tell me that the right click is related to the copy function and suggested me to comment out "doc.parentWindow.clipboardData.getData". This disable copy and paste but lets you use the right click.

So seems to be necessary a change in the SMP's author side in order to make this feature works.


Let's see if we can also get a conversation going on with @Wil-B.

Thank you very much for trying!

-TT

@fwn0
Copy link

fwn0 commented Nov 22, 2021

I can confirm on Artix (Linux) with latest stable WINE x86 in Appimage.
Biography is unable to download (in yttm folder) and display images in biography, text is OK.
Library and biography crashes when right click in panel, and modal crash when opening library and biography options dialog window.

@Wil-B
Copy link

Wil-B commented Nov 22, 2021

Regarding saving images, as noted above, the first part of the process is to call windows cscript that's run through ActiveXObject('WScript.Shell'). This in turn calls an external .vbs file that streams and saves the images. To do this, the .vbs file uses a number of further objects:

Createobject("Scripting.FileSystemObject")

CreateObject("MSXML2.XMLHTTP")

CreateObject("ADODB.Stream")

I guess as these are in an external file they're normally run by windows & may be independent of SMP.

I think it would need someone with coding skills running Linux (which I don't have & so can't test anything) to debug exactly where it's failing and to see if it's possible to work around it. Otherwise SMP may need to be updated with new methods.

Right-click error is due to: doc.parentWindow.clipboardData.getData, where doc is ActiveXObject('htmlfile'). So the error seems to be due to some issue with that ActiveXObject method in Linux . It's used to paste from clipboard. So another way of accessing the clipboard may be needed, or comment out usage.

Options uses the SMP ShowHtmlDialog. ShowHtmlDialog v2 is in SMP current tasks and plans.

@razielanarki
Copy link
Contributor Author

razielanarki commented Nov 27, 2021

Regarding saving images, as noted above, the first part of the process is to call windows cscript that's run through ActiveXObject('WScript.Shell'). This in turn calls an external .vbs file that streams and saves the images. To do this, the .vbs file uses a number of further objects:

Createobject("Scripting.FileSystemObject")

CreateObject("MSXML2.XMLHTTP")

CreateObject("ADODB.Stream")

I guess as these are in an external file they're normally run by windows & may be independent of SMP.

I think it would need someone with coding skills running Linux (which I don't have & so can't test anything) to debug exactly where it's failing and to see if it's possible to work around it. Otherwise SMP may need to be updated with new methods.

Right-click error is due to: doc.parentWindow.clipboardData.getData, where doc is ActiveXObject('htmlfile'). So the error seems to be due to some issue with that ActiveXObject method in Linux . It's used to paste from clipboard. So another way of accessing the clipboard may be needed, or comment out usage.

Options uses the SMP ShowHtmlDialog. ShowHtmlDialog v2 is in SMP current tasks and plans.

@TT-ReBORN @Wil-B hi!
i did a quick skim over the issues, and nit seems that

you can reach me at hyrdogenaudio via: https://hydrogenaud.io/index.php?action=profile;u=142642 (and pkz drop me a msg withthe topic link there, i may just miss is being just that featherheaded and aop sometimes :) TY! )

@razielanarki
Copy link
Contributor Author

razielanarki commented Nov 27, 2021

aaaaand i just realized that last time (actually before that) i missed to upload the scripts whitch i used to test the: Directwrite and FontCache / Metrics
stuff (both require a component compiled form these sources, but nothing else, just std smp flags.js stuff)
text-tests.zip
screen:

  • top: dwrite/gdi/gdip+ metrics, font cache (scroll to change the fotb sie, a-s-d-f-g-h + j change fonts, X to flip the -"emheight"/charsize" sign , C to log the contents of cache
    it seems a lot of un-collecterg garbage gdifonts get struck in the 1GB defautl gc alloc limit @TheQwertiest do i read this right? the win32handle limit is somewhere 10-16K (gdi and other) handles / process)...
    and L to log the metrics of the current font/size (also top left: "invalid font name/0-size" "default" font GDI font mapping, in gdi/dw)
  • bottom testing the horribly complex (argh) writetext and writesritng methods to be api compatible with gdidrawtext and drawstring, and "COLR" fonts.
    image

@TT-ReBORN
Copy link

TT-ReBORN commented Nov 27, 2021

@razielanarki @TheQwertiest @Wil-B @fwn0

new thread on HydrogenAudio started here:
https://hydrogenaud.io/index.php?topic=121786.0

Hi razielanarki,
I know already ( another linux user and myself already tried ) about Winetricks and ie8 and all the links you've posted.
This unfortunately does nothing... best would be if you could get in touch with the Wine dev Damjan Jovanovic.
His contact is damjan.jov@gmail.com.

Edit: I have tested it on the latest Wine 6.02 stable . The xml controls you've mentioned is it 6.02 stable or 6.22 dev?

Thanks!

-TT

@TheQwertiest
Copy link
Owner

Ok, I'm back for the time being, so I'll at least have a look at what you have accomplished here :)

@TheQwertiest
Copy link
Owner

TheQwertiest commented Jan 14, 2022

i wanted to ask @TheQwertiest , if i may: do you know any online mirrors

the only available docs are in header files of mozjs and in their example repo

@TheQwertiest
Copy link
Owner

these latter two "bigger" features should be relatively easily extracted, however, this PR begins feels more and more like a feature creep

Yeah, it will have to be separated at some point of the time, but we can keep at as is for the time being

@marc2k3
Copy link
Contributor

marc2k3 commented Jan 14, 2022

On the subject of gdiplus, the latest windows SDKs (20348 / 22000) have a fix for the min/max issue in gdiplustypes.h meaning this can be nuked from stdafx.h

namespace Gdiplus
{
using std::min;
using std::max;
};

It only took them about 20 years or something.

@marc2k3
Copy link
Contributor

marc2k3 commented Feb 3, 2022

additionally, if the textLayout range-foramtting api were to be exposed,
"rich-text" with multi-color/font/styls (html/lyrics/etc) could be easily rendered in one call,

I'm implementing this right now. I posted a little preview here...

https://hydrogenaud.io/index.php?topic=110499.msg1007769#msg1007769

@razielanarki
Copy link
Contributor Author

I'm implementing this right now. I posted a little preview here...

https://hydrogenaud.io/index.php?topic=110499.msg1007769#msg1007769

cool. coolcoolcool :)

i've been lurking around and wanted to check out foo_jscript_panel (and the api you've implemented), but the repo seems like it's been archived? :(

@marc2k3
Copy link
Contributor

marc2k3 commented Feb 12, 2022

I've essentially ended development on that version because gdi/d2d interop doesn't seem feasible. I was using a DC render target and calling BindDC / BeginDraw / EndDraw on every function call hammered performance (it was fine for 99% of use cases but scrolling a playlist was not nice). Maybe there was an easier solution but I'm working on a new version which nukes all gdi support. Not even sure I'll release it.

But I can post a few code snippets of how I implemented that text range.... First the JS...

function RGB(r, g, b) {
	return (0xff000000 | (r << 16) | (g << 8) | (b));
}

var fonts = utils.ListFonts().toArray();
var text = utils.ReadUTF8("E:\\Applications\\foobar2000\\profile\\js_data\\artists\\Little Scream\\allmusic.The Golden Record.txt");
var arr = text.split(" "); // split text in to whole words for styling
var styles = [];

refresh();

function refresh() {
	styles = [];
	var start = 0;
	arr.forEach(function(item, i) {
		styles.push({
			start : start,
			length : item.length + 1,
			font_name : fonts[Math.floor(Math.random() * fonts.length)],
			font_size : 12 + Math.floor(Math.random() * 20),
			font_weight : Math.round(Math.random() * 800) + 100, // values between 100-900
			colour : RGB(Math.random() * 200, Math.random() * 200, Math.random() * 200),
			underline : Math.random() < 0.1,
			strikethrough : Math.random() < 0.1,
		});
		start += item.length + 1;
	});
}

function on_paint(gr) {
	gr.WriteText(text, JSON.stringify(styles), 0, 10, 10, window.Width - 20, window.Height - 20);
}

window.SetInterval(function () {
	refresh();
	window.Repaint();
}, 500);

Then translating the JSON to DWRITE_TEXT_RANGE

If the JSON is an object, just apply the style to the whole range.
If the JSON is an array, each element must have a start/length value.

HRESULT JSGraphics::apply_styles(IDWriteTextLayout* text_layout, const std::wstring& styles, uint32_t total_length)
{
	json j = json::parse(from_wide(styles), nullptr, false);
	if (j.is_object())
	{
		DWRITE_TEXT_RANGE range(0, total_length);
		RETURN_IF_FAILED(apply_style(text_layout, j, range));
	}
	else if (j.is_array())
	{
		for (auto&& obj : j)
		{
			if (!obj.is_object()) return E_INVALIDARG;
			auto start = obj["start"];
			auto length = obj["length"];
			if (!start.is_number_unsigned() || !length.is_number_unsigned()) return E_INVALIDARG;

			DWRITE_TEXT_RANGE range(start.get<uint32_t>(), length.get<uint32_t>());
			RETURN_IF_FAILED(apply_style(text_layout, obj, range));
		}
	}
	return S_OK;
}

then parsing each range...

HRESULT JSGraphics::apply_style(IDWriteTextLayout* text_layout, json j, DWRITE_TEXT_RANGE range)
{
	auto font_name = j["font_name"];
	if (font_name.is_string())
	{
		auto tmp = to_wide(font_name.get<std::string>());
		RETURN_IF_FAILED(text_layout->SetFontFamilyName(tmp.data(), range));
	}

	auto font_size = j["font_size"];
	if (font_size.is_number_unsigned())
	{
		auto tmp = to_float(font_size.get<uint32_t>());
		RETURN_IF_FAILED(text_layout->SetFontSize(tmp, range));
	}

	auto font_weight = j["font_weight"];
	if (font_weight.is_number_unsigned())
	{
		auto tmp = static_cast<DWRITE_FONT_WEIGHT>(font_weight.get<uint32_t>());
		RETURN_IF_FAILED(text_layout->SetFontWeight(tmp, range));
	}

	auto font_style = j["font_style"];
	if (font_style.is_number_unsigned())
	{
		auto tmp = static_cast<DWRITE_FONT_STYLE>(font_style.get<uint32_t>());
		RETURN_IF_FAILED(text_layout->SetFontStyle(tmp, range));
	}

	auto font_stretch = j["font_stretch"];
	if (font_stretch.is_number_unsigned())
	{
		auto tmp = static_cast<DWRITE_FONT_STRETCH>(font_stretch.get<uint32_t>());
		RETURN_IF_FAILED(text_layout->SetFontStretch(tmp, range));
	}

	auto strikethrough = j["strikethrough"];
	if (strikethrough.is_boolean())
	{
		auto tmp = strikethrough.get<bool>() ? TRUE : FALSE;
		RETURN_IF_FAILED(text_layout->SetStrikethrough(tmp, range));
	}

	auto underline = j["underline"];
	if (underline.is_boolean())
	{
		auto tmp = underline.get<bool>() ? TRUE : FALSE;
		RETURN_IF_FAILED(text_layout->SetUnderline(tmp, range));
	}

	auto colour = j["colour"];
	if (colour.is_number())
	{
		auto tmp = colour.get<int64_t>();
		wil::com_ptr_t<ID2D1SolidColorBrush> brush;
		RETURN_IF_FAILED(m_render_target->CreateSolidColorBrush(to_colorf(tmp), &brush));
		RETURN_IF_FAILED(text_layout->SetDrawingEffect(brush.get(), range));
	}
	return S_OK;
}

@razielanarki
Copy link
Contributor Author

neat, thanks!

on a (maybe moot point) side note, i believe you should only call BindDC once (maybe bind to a panel HWND sized memory dc via CreateCompatibleDC), and update using gdi blitting (i'm doing something similar in https://github.com/razielanarki/foo_spider_monkey_panel/blob/674a01f006e748a8c8b1bdc1f8a39dccf2b51862/foo_spider_monkey_panel/utils/dwrite_renderer.cpp#L231-L291, however dwrite handles the d2d context for me in the background, but there are functions like https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getboundsrect that can retrieve the changed boundsrect to update back.. i've only stubled upon it when i've implemented the text-bounds-clipped-back-blitting above, lol)

in the meantime i've took an ever deeper dive into mozilla codebase to see how they did the js binding via DOM/XPCOM, as i believe there's an solution buried in there somewhere for wrapping+exposing COM apis to JS, cuz i like how neat the .idl definitions are in jscript_panel (not to mention the usefulness for foobar skin developers if a common api could be easily shared between foo_XYZ_scripting_panel implementations).

(ps: have you seen microsoft clearscript? too bad it's mostly .net/C# only...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants