See the question and my original answer on StackOverflow

Here is a fully working example, using ID2D1PathGeometry1::ComputePointAndSegmentAtLength that allows you to get points coordinates of a path from a length parameter. length represents the point on the path which is "at length distance" along this path from its start.

The sample code uses a custom IDWriteTextRenderer implementation which is used to render each glyph along the path. This sample implementation mostly implements IDWriteTextRenderer::DrawGlyphRun for the whole string, which in turns re-uses ID2D1RenderTarget::DrawGlyphRun for each glyph after appropriate translation and rotation.

#define _USE_MATH_DEFINES
#include <windows.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <winrt/base.h>

using namespace winrt;

com_ptr<ID2D1Factory1> _d2dFactory;
com_ptr<ID2D1HwndRenderTarget> _renderTarget;
com_ptr<ID2D1SolidColorBrush> _red;
com_ptr<ID2D1SolidColorBrush> _black;
com_ptr<ID2D1PathGeometry1> _geometry;
com_ptr<IDWriteTextLayout> _layout;

wchar_t _phrase[] = L"Quick brown fox jumps over the lazy dog";

class Renderer : public IDWriteTextRenderer
{
public:
  STDMETHOD(DrawGlyphRun)(void* clientDrawingContext, // should be used to pass RenderTarget, Geometry, etc.
    FLOAT baselineOriginX, FLOAT baselineOriginY,
    DWRITE_MEASURING_MODE measuringMode,
    DWRITE_GLYPH_RUN const* glyphRun,
    DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription, IUnknown* clientDrawingEffect
    )
  {
    auto advance = baselineOriginX;
    for (UINT32 i = 0; i < glyphRun->glyphCount; i++)
    {
      D2D1_POINT_DESCRIPTION desc;
      check_hresult(_geometry->ComputePointAndSegmentAtLength(advance, 0, nullptr, &desc));
      if (desc.endFigure) // path ends here, we could break
      {
        // continue with a small back out
        advance -= desc.lengthToEndSegment;
        check_hresult(_geometry->ComputePointAndSegmentAtLength(advance, 0, nullptr, &desc));
      }

      advance += glyphRun->glyphAdvances[i];

      // compute angle
      auto angle = M_PI_2 - atan2(desc.unitTangentVector.x, desc.unitTangentVector.y);

      // translate & rotate
      auto baseLineTx = D2D1::Matrix3x2F::Translation(0, glyphRun->fontEmSize - baselineOriginY);
      auto tx = D2D1::Matrix3x2F::Translation(desc.point.x, desc.point.y);
      auto rot = D2D1::Matrix3x2F::Rotation((FLOAT)(angle * 180 / M_PI));
      _renderTarget->SetTransform(baseLineTx * rot * tx);

      // draw 1 glyph
      DWRITE_GLYPH_RUN run = *glyphRun;
      run.glyphCount = 1;
      run.glyphAdvances = &glyphRun->glyphAdvances[i];
      run.glyphIndices = &glyphRun->glyphIndices[i];
      run.glyphOffsets = &glyphRun->glyphOffsets[i];
      _renderTarget->DrawGlyphRun(D2D1_POINT_2F(), &run, _black.get(), measuringMode);
    }

    _renderTarget->SetTransform(D2D1::IdentityMatrix()); // make sure we come back to no transform
    return S_OK;
  }

  STDMETHOD(IsPixelSnappingDisabled)(void* clientDrawingContext, BOOL* isDisabled)
  {
    *isDisabled = TRUE;
    return S_OK;
  }

  // nothing is implemented except IUnknown
  STDMETHOD(GetCurrentTransform)(void* clientDrawingContext, DWRITE_MATRIX* transform) { return E_NOTIMPL; }
  STDMETHOD(GetPixelsPerDip)(void* clientDrawingContext, FLOAT* pixelsPerDip) { return E_NOTIMPL; }
  STDMETHOD(DrawUnderline)(void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_UNDERLINE const* underline, IUnknown* clientDrawingEffect) { return E_NOTIMPL; }
  STDMETHOD(DrawStrikethrough)(void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_STRIKETHROUGH const* strikethrough, IUnknown* clientDrawingEffect) { return E_NOTIMPL; }
  STDMETHOD(DrawInlineObject)(void* clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject* inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown* clientDrawingEffect) { return E_NOTIMPL; }
  STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject)
  {
    if (riid == __uuidof(IUnknown)) { *ppvObject = (IUnknown*)this; return S_OK; }
    if (riid == __uuidof(IDWritePixelSnapping)) { *ppvObject = (IDWritePixelSnapping*)this; return S_OK; }
    if (riid == __uuidof(IDWriteTextRenderer)) { *ppvObject = (IDWriteTextRenderer*)this; return S_OK; }
    *ppvObject = nullptr;
    return E_NOINTERFACE;
  }

  ULONG AddRef() { return 1; }
  ULONG Release() { return 1; }
};

Renderer _renderer{ };

void Render()
{
  _renderTarget->BeginDraw();
  _renderTarget->Clear(D2D1_COLOR_F(1, 1, 1));

  // draw geometry
  _renderTarget->DrawGeometry(_geometry.get(), _red.get(), 4);

  // draw phrase using our special render
  // with some start offset (250)
  // and some baseline offset (10)
  check_hresult(_layout->Draw(nullptr, &_renderer, 250, 10));
  check_hresult(_renderTarget->EndDraw());
}

void BuildGeometry(FLOAT width, FLOAT height)
{
  auto margin = 60.0f;
  width -= 2 * margin;
  height -= 2 * margin;
  if (width <= 0 || height <= 0)
    return;

  _geometry = nullptr;
  check_hresult(_d2dFactory->CreatePathGeometry(_geometry.put()));
  com_ptr<ID2D1GeometrySink> sink;
  check_hresult(_geometry->Open(sink.put()));
  auto size = D2D_SIZE_F(width / 4, height / 2);
  sink->BeginFigure(D2D_POINT_2F(margin + width / 4, margin), D2D1_FIGURE_BEGIN_FILLED);
  sink->AddArc(D2D1_ARC_SEGMENT(D2D_POINT_2F(margin + width / 4, margin + height), size));
  sink->AddLine(D2D_POINT_2F(margin + width * 3 / 4, margin + height));
  sink->AddArc(D2D1_ARC_SEGMENT(D2D_POINT_2F(margin + width * 3 / 4, margin), size));
  sink->EndFigure(D2D1_FIGURE_END_CLOSED);
  check_hresult(sink->Close());
}

void Resize(UINT width, UINT height)
{
  winrt::check_hresult(_renderTarget->Resize(D2D1_SIZE_U(width, height)));
  BuildGeometry((FLOAT)width, (FLOAT)height);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
  case WM_PAINT:
  {
    PAINTSTRUCT ps;
    BeginPaint(hwnd, &ps);
    Render();
    EndPaint(hwnd, &ps);
    return 0;
  }

  case WM_SIZE:
    Resize(LOWORD(lParam), HIWORD(lParam));
    return 0;

  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc = WndProc;
  wcex.hInstance = hInstance;
  wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
  wcex.lpszClassName = L"App";
  RegisterClassEx(&wcex);

  auto hwnd = CreateWindow(L"App", L"App", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 400, nullptr, nullptr, hInstance, nullptr);

  // init some D2D & DWrite
  check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _d2dFactory.put()));
  check_hresult(_d2dFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd), _renderTarget.put()));
  check_hresult(_renderTarget->CreateSolidColorBrush(D2D1::ColorF(1, 0, 0), _red.put()));
  check_hresult(_renderTarget->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0), _black.put()));

  com_ptr<IDWriteFactory> dwriteFactory;
  check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)dwriteFactory.put()));
  com_ptr<IDWriteTextFormat> format;
  check_hresult(dwriteFactory->CreateTextFormat(L"Times New Roman", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 50, L"", format.put()));

  // we want a straight line w/o limit
  check_hresult(dwriteFactory->CreateTextLayout(_phrase, lstrlen(_phrase), format.get(), FLT_MAX, 0, _layout.put()));

  ShowWindow(hwnd, SW_SHOWNORMAL);
  UpdateWindow(hwnd);
  MSG msg;
  while (GetMessage(&msg, nullptr, 0, 0) > 0)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return 0;
}

And here is the result:

enter image description here

With 250px start offset and 10px baseline offset:

enter image description here