Skip to content

Commit 6e04a2a

Browse files
committed
- Ensure at least one drawable object is visible when selecting a font family, in case the app was started with /blank.
- Ensure drawable objects are selected even if the UI control still has an empty list due to not being refreshed yet. - Add functionality to retrieve a bounding box of a glyph run.
1 parent 93f47fb commit 6e04a2a

2 files changed

Lines changed: 320 additions & 1 deletion

File tree

DWritEx.ixx

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,3 +2407,309 @@ D2D1_RECT_F GetBlackBox(const DWRITE_OVERHANG_METRICS& overhangMetrics, const DW
24072407
/*bottom*/ overhangMetrics.bottom + textMetrics.layoutHeight
24082408
};
24092409
}
2410+
2411+
2412+
HRESULT GetGlyphMetrics(
2413+
IDWriteFontFace* fontFace,
2414+
float fontEmSize,
2415+
float pixelsPerDip,
2416+
const DWRITE_MATRIX* transform,
2417+
DWRITE_MEASURING_MODE measuringMode,
2418+
bool isSideways,
2419+
uint32_t glyphCount,
2420+
_In_reads_(glyphCount) const uint16_t* glyphIndices,
2421+
_Out_writes_(glyphCount) DWRITE_GLYPH_METRICS* glyphMetrics
2422+
)
2423+
{
2424+
switch (measuringMode)
2425+
{
2426+
case DWRITE_MEASURING_MODE_GDI_CLASSIC:
2427+
case DWRITE_MEASURING_MODE_GDI_NATURAL:
2428+
IFR(fontFace->GetGdiCompatibleGlyphMetrics(
2429+
fontEmSize,
2430+
pixelsPerDip,
2431+
transform,
2432+
(measuringMode == DWRITE_MEASURING_MODE_GDI_NATURAL),
2433+
glyphIndices,
2434+
glyphCount,
2435+
OUT glyphMetrics,
2436+
isSideways
2437+
));
2438+
break;
2439+
2440+
default:
2441+
DEBUG_ASSERT("A new measuring mode has been added. Handle it here.");
2442+
__fallthrough;
2443+
2444+
case DWRITE_MEASURING_MODE_NATURAL:
2445+
IFR(fontFace->GetDesignGlyphMetrics(
2446+
glyphIndices,
2447+
glyphCount,
2448+
OUT glyphMetrics,
2449+
isSideways
2450+
));
2451+
break;
2452+
}
2453+
2454+
return S_OK;
2455+
}
2456+
2457+
2458+
DWRITE_GLYPH_ORIENTATION_ANGLE GetRelativeOrientation(bool isSideways, bool isFlippedOrientation) throw()
2459+
{
2460+
// Determine a relative angle from the flags.
2461+
//
2462+
// isSideways isFlipped -> Angle
2463+
// F F 0
2464+
// T T 90
2465+
// F T 180
2466+
// T F 270
2467+
2468+
DWRITE_GLYPH_ORIENTATION_ANGLE rotationAmount = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES;
2469+
if (isSideways) rotationAmount = DWRITE_GLYPH_ORIENTATION_ANGLE(rotationAmount + 1);
2470+
if (isSideways != isFlippedOrientation) rotationAmount = DWRITE_GLYPH_ORIENTATION_ANGLE(rotationAmount + 2);
2471+
2472+
return rotationAmount;
2473+
}
2474+
2475+
2476+
void AccumulateRect(_Inout_ D2D1_RECT_F& modifiedRect, D2D1_RECT_F const& otherRect)
2477+
{
2478+
if (otherRect.left < modifiedRect.left) modifiedRect.left = otherRect.left;
2479+
if (otherRect.right > modifiedRect.right) modifiedRect.right = otherRect.right;
2480+
if (otherRect.top < modifiedRect.top) modifiedRect.top = otherRect.top;
2481+
if (otherRect.bottom > modifiedRect.bottom) modifiedRect.bottom = otherRect.bottom;
2482+
}
2483+
2484+
2485+
inline void OffsetRect(_Inout_ D2D1_RECT_F& modifiedRect, float x, float y)
2486+
{
2487+
modifiedRect.left += x;
2488+
modifiedRect.top += y;
2489+
modifiedRect.right += x;
2490+
modifiedRect.bottom += y;
2491+
}
2492+
2493+
2494+
template<typename RectType>
2495+
void RotateRect(
2496+
_Inout_ RectType& modifiedRect,
2497+
DWRITE_GLYPH_ORIENTATION_ANGLE rotationAmount
2498+
)
2499+
{
2500+
// Rotate the corners around the zero point <0,0>, whether it be a glyph
2501+
// or inline object.
2502+
switch (rotationAmount)
2503+
{
2504+
case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES:
2505+
break; // do nothing
2506+
2507+
case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES:
2508+
{
2509+
auto oldLeft = modifiedRect.left;
2510+
modifiedRect.left = modifiedRect.bottom;
2511+
modifiedRect.bottom = -modifiedRect.right;
2512+
modifiedRect.right = modifiedRect.top;
2513+
modifiedRect.top = -oldLeft;
2514+
}
2515+
break;
2516+
2517+
case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES: // for stacked Arabic
2518+
{
2519+
std::swap(modifiedRect.left, modifiedRect.right);
2520+
std::swap(modifiedRect.top, modifiedRect.bottom);
2521+
modifiedRect.left = -modifiedRect.left;
2522+
modifiedRect.right = -modifiedRect.right;
2523+
modifiedRect.top = -modifiedRect.top;
2524+
modifiedRect.bottom = -modifiedRect.bottom;
2525+
}
2526+
break;
2527+
2528+
case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES: // for ideographs (isSideways = true)
2529+
{
2530+
auto oldLeft = modifiedRect.left;
2531+
modifiedRect.left = modifiedRect.top;
2532+
modifiedRect.top = -modifiedRect.right;
2533+
modifiedRect.right = modifiedRect.bottom;
2534+
modifiedRect.bottom = -oldLeft;
2535+
}
2536+
break;
2537+
2538+
default:
2539+
DEBUG_ASSERT("Update the code to handle the new orientation.");
2540+
}
2541+
}
2542+
2543+
2544+
// Checks whether a glyph has a non-empty black box.
2545+
inline bool GlyphHasSize(DWRITE_GLYPH_METRICS const& glyphMetrics) throw()
2546+
{
2547+
return static_cast<int64_t>(glyphMetrics.advanceWidth) - glyphMetrics.leftSideBearing - glyphMetrics.rightSideBearing > 0
2548+
&& static_cast<int64_t>(glyphMetrics.advanceHeight) - glyphMetrics.topSideBearing - glyphMetrics.bottomSideBearing > 0;
2549+
}
2550+
2551+
2552+
HRESULT GetGlyphRunBlackBox(
2553+
float baselineOriginX,
2554+
float baselineOriginY,
2555+
IDWriteFontFace* fontFace,
2556+
float fontEmSize,
2557+
float pixelsPerDip,
2558+
const DWRITE_MATRIX* transform,
2559+
DWRITE_MEASURING_MODE measuringMode,
2560+
bool isRtlContent,
2561+
bool isRtlSpan,
2562+
bool isSideways,
2563+
uint32_t glyphCount,
2564+
_In_reads_(glyphCount) const uint16_t* glyphIndices,
2565+
_In_reads_(glyphCount) const float* glyphAdvances,
2566+
_In_reads_opt_(glyphCount) const DWRITE_GLYPH_OFFSET* glyphOffsets,
2567+
_Inout_ D2D1_RECT_F& blackBox
2568+
)
2569+
{
2570+
if (glyphCount <= 0)
2571+
{
2572+
return S_OK;
2573+
}
2574+
2575+
DWRITE_FONT_METRICS fontMetrics = {};
2576+
fontFace->GetMetrics(&fontMetrics);
2577+
2578+
DWRITE_GLYPH_METRICS smallBuffer[120];
2579+
std::vector<DWRITE_GLYPH_METRICS> bigBuffer;
2580+
DWRITE_GLYPH_METRICS* glyphRunMetrics = smallBuffer;
2581+
2582+
if (glyphCount > std::size(smallBuffer))
2583+
{
2584+
bigBuffer.resize(glyphCount);
2585+
glyphRunMetrics = &bigBuffer[0];
2586+
}
2587+
2588+
IFR(GetGlyphMetrics(
2589+
fontFace,
2590+
fontEmSize,
2591+
pixelsPerDip,
2592+
transform,
2593+
measuringMode,
2594+
isSideways,
2595+
glyphCount,
2596+
glyphIndices,
2597+
OUT glyphRunMetrics
2598+
));
2599+
2600+
D2D1_RECT_F accumulatedBounds;
2601+
float baselineX = baselineOriginX;
2602+
float const baselineY = baselineOriginY;
2603+
float const fontScale = fontEmSize / fontMetrics.designUnitsPerEm;
2604+
2605+
// Determine how to rotate the blackbox of each glyph from the flags.
2606+
2607+
bool const isFlippedOrientation = (isRtlContent != isRtlSpan);
2608+
DWRITE_GLYPH_ORIENTATION_ANGLE const rotationAmount = GetRelativeOrientation(isSideways, isFlippedOrientation);
2609+
2610+
for (uint32_t j = 0; j < glyphCount; ++j)
2611+
{
2612+
DWRITE_GLYPH_METRICS const& glyphMetrics = glyphRunMetrics[j];
2613+
float const nominalGlyphAdvance = (isSideways ? glyphMetrics.advanceHeight : glyphMetrics.advanceWidth) * fontScale;
2614+
float const glyphAdvance = (glyphAdvances != nullptr) ? glyphAdvances[j] : nominalGlyphAdvance;
2615+
2616+
float glyphX = baselineX, glyphY = baselineY;
2617+
2618+
// When glyphs are drawn RTL, the glyph origin is on the opposite
2619+
// side of the pen. So add the nominal advance to get the glyph origin
2620+
// (the nominal advance, not the user supplied advance). The sign
2621+
// depends on whether the glyphs are increasing/decreasing in
2622+
// coordinate space which depends on the direction of the span
2623+
// rather than the content (since upside-down RTL text is effectively
2624+
// LTR).
2625+
if (isRtlContent)
2626+
{
2627+
glyphX += isRtlSpan ? -nominalGlyphAdvance : nominalGlyphAdvance;
2628+
}
2629+
// Advance the pen.
2630+
baselineX += isRtlSpan ? -glyphAdvance : glyphAdvance;
2631+
2632+
// Skip any blank glyphs like the space character, which has no black box.
2633+
if (!GlyphHasSize(glyphMetrics))
2634+
continue;
2635+
2636+
// For sideways glyphs like ideographs in vertical, move from the pen
2637+
// position (which is relative to the vertical origin) to the glyph
2638+
// origin at the bottom left (0,0 in font design space). The delta
2639+
// from the vertical origin to the horizontal origin is the vertical
2640+
// origin distance and half the advance width.
2641+
if (isSideways)
2642+
{
2643+
// If the glyph run is flipped relative to the line, then reverse
2644+
// the signs, since the glyph is turned 180 degrees relative to
2645+
// the coordinate space.
2646+
int32_t originY = glyphMetrics.verticalOriginY;
2647+
int32_t advance = glyphMetrics.advanceWidth;
2648+
glyphX += (isFlippedOrientation ? -originY : originY) * fontScale;
2649+
glyphY += (isFlippedOrientation ? -advance : advance) * fontScale * 0.5f;
2650+
}
2651+
2652+
// Determine ink edges of glyph (in font design units but Cartesian coordinates).
2653+
// The four corners are relative to the horizontal glyph origin now.
2654+
D2D_RECT_L intBounds = {
2655+
int32_t(glyphMetrics.leftSideBearing),
2656+
int32_t(glyphMetrics.topSideBearing - glyphMetrics.verticalOriginY),
2657+
int32_t(glyphMetrics.advanceWidth - glyphMetrics.rightSideBearing),
2658+
int32_t(glyphMetrics.advanceHeight - glyphMetrics.bottomSideBearing - glyphMetrics.verticalOriginY)
2659+
};
2660+
2661+
RotateRect(IN OUT intBounds, rotationAmount);
2662+
2663+
// Add the glyph offset from shaping/character spacing/justification.
2664+
if (glyphOffsets != nullptr)
2665+
{
2666+
float advanceOffset = glyphOffsets[j].advanceOffset;
2667+
float ascenderOffset = glyphOffsets[j].ascenderOffset;
2668+
glyphX += isRtlSpan ? -advanceOffset : advanceOffset;
2669+
glyphY += isFlippedOrientation ? ascenderOffset : -ascenderOffset;
2670+
}
2671+
2672+
// Scale the font design units by the font size to get DIPs
2673+
// and add the glyph position to move from glyph space to layout space.
2674+
D2D1_RECT_F glyphBounds = {
2675+
float(intBounds.left) * fontScale,
2676+
float(intBounds.top) * fontScale,
2677+
float(intBounds.right) * fontScale,
2678+
float(intBounds.bottom) * fontScale
2679+
};
2680+
OffsetRect(IN OUT glyphBounds, glyphX, glyphY);
2681+
2682+
AccumulateRect(IN OUT accumulatedBounds, glyphBounds);
2683+
}
2684+
2685+
AccumulateRect(IN OUT blackBox, accumulatedBounds);
2686+
2687+
return S_OK;
2688+
}
2689+
2690+
2691+
HRESULT GetGlyphRunBlackBox(
2692+
DWRITE_GLYPH_RUN const& glyphRun,
2693+
float baselineOriginX,
2694+
float baselineOriginY,
2695+
_Inout_ D2D1_RECT_F& blackBox
2696+
)
2697+
{
2698+
return GetGlyphRunBlackBox(
2699+
baselineOriginX,
2700+
baselineOriginY,
2701+
glyphRun.fontFace,
2702+
glyphRun.fontEmSize,
2703+
1, // pixelsPerDip
2704+
nullptr, // transform
2705+
DWRITE_MEASURING_MODE_NATURAL,
2706+
glyphRun.bidiLevel & 1,
2707+
glyphRun.bidiLevel & 1,
2708+
glyphRun.isSideways,
2709+
glyphRun.glyphCount,
2710+
glyphRun.glyphIndices,
2711+
glyphRun.glyphAdvances,
2712+
glyphRun.glyphOffsets,
2713+
OUT blackBox
2714+
);
2715+
}

MainWindow.ixx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1244,7 +1244,18 @@ MainWindow::DialogProcResult CALLBACK MainWindow::OnDragAndDrop(HWND hwnd, UINT
12441244

12451245
std::vector<uint32_t> MainWindow::GetSelectedDrawableObjectIndices()
12461246
{
1247-
return GetListViewMatchingIndices(GetWindowFromId(hwnd_, IdcDrawableObjectsList), LVNI_SELECTED, /*returnAllIfNoMatch*/true);
1247+
auto selectedIndices = GetListViewMatchingIndices(GetWindowFromId(hwnd_, IdcDrawableObjectsList), LVNI_SELECTED, /*returnAllIfNoMatch*/true);
1248+
1249+
// Return all the indices if none are selected, or if none exist in the list control
1250+
// due to the drawableObjects_ array being sized before the IdcDrawableObjectsList control
1251+
// was updated.
1252+
if (selectedIndices.empty())
1253+
{
1254+
selectedIndices.resize(drawableObjects_.size());
1255+
std::iota(selectedIndices.begin(), selectedIndices.end(), 0);
1256+
}
1257+
1258+
return selectedIndices;
12481259
}
12491260

12501261

@@ -1532,6 +1543,8 @@ UINT_PTR CALLBACK ChooseFontHookProc(
15321543

15331544
HRESULT MainWindow::SelectFontFamily()
15341545
{
1546+
EnsureAtLeastOneDrawableObject();
1547+
15351548
LOGFONT logFont = {};
15361549
GetLogFontFromDrawableObjects(OUT logFont);
15371550

0 commit comments

Comments
 (0)