Well, I found a solution that works for me.
I duplicated the code in TabBarButton::paintButton(), using an empty string instead of getButtonText(), then use LookAndFeel::drawTabButtonText() to put the text where I want it.
There is still a problem with where to keep the icon index. It seems to belong naturally in MyExtendedTabBarButton, but that leads to difficulties when the icons are updated: the natural thing would be to walk the tabs in index order, updating the icons in turn, to reflect whatever state the page is in.
Unfortunately, the TabbedButtonBar::getTabButton(int index) is private and its implementation is a bit strange: TabbedButtonBar class doesn’t keep track of the buttons, but keeps an array of strings and an array of colours which are manipulated in parallel, so, to get the tab at a particular index, a dynamic_cast-based component search is performed. I.e. a tab walk will use a quadratic number of dynamic casts.
IMO the design would be simpler and faster if TabBarButton got a colour field and TabbedButtonBar simply held one Array<TabBarButton*>.