//============================================================================== void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, const Justification& justification) const { if (text.isNotEmpty() && startX < context.getClipBounds().getRight()) { GlyphArrangement arr; arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); // Don't pass any vertical placement flags to this method - they'll be ignored. jassert (justification.getOnlyVerticalFlags() == 0); const int flags = justification.getOnlyHorizontalFlags(); if (flags != Justification::left) { float w = arr.getBoundingBox (0, -1, true).getWidth(); if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) w /= 2.0f; arr.draw (*this, AffineTransform::translation (-w, 0)); } else { arr.draw (*this); } } }
String justificationToCode (Justification justification) { switch (justification.getFlags()) { case Justification::centred: return "Justification::centred"; case Justification::centredLeft: return "Justification::centredLeft"; case Justification::centredRight: return "Justification::centredRight"; case Justification::centredTop: return "Justification::centredTop"; case Justification::centredBottom: return "Justification::centredBottom"; case Justification::topLeft: return "Justification::topLeft"; case Justification::topRight: return "Justification::topRight"; case Justification::bottomLeft: return "Justification::bottomLeft"; case Justification::bottomRight: return "Justification::bottomRight"; case Justification::left: return "Justification::left"; case Justification::right: return "Justification::right"; case Justification::horizontallyCentred: return "Justification::horizontallyCentred"; case Justification::top: return "Justification::top"; case Justification::bottom: return "Justification::bottom"; case Justification::verticallyCentred: return "Justification::verticallyCentred"; case Justification::horizontallyJustified: return "Justification::horizontallyJustified"; default: break; } jassertfalse; return "Justification (" + String (justification.getFlags()) + ")"; }
//------------------------------------------------------------------------------ void MiscPreferences::drawGroupComponentOutline(Graphics& g, int width, int height, const String& text, const Justification& position) { const float textH = 15.0f; const float indent = 3.0f; const float textEdgeGap = 4.0f; float cs = 5.0f; Font f (textH, Font::bold); Path p; float x = indent; float y = f.getAscent() - 3.0f; float w = width - x * 2.0f; float h = height - y - indent; cs = jmin (cs, w * 0.5f, h * 0.5f); const float cs2 = 2.0f * cs; float textW = jlimit (0.0f, f.getStringWidth (text) + textEdgeGap * 2.0f, w - cs2); float textX = cs + textEdgeGap; if (position.testFlags (Justification::verticallyCentred)) textX = cs + (w - cs2 - textW) * 0.5f; else if (position.testFlags (Justification::right)) textX = w - cs - textW - textEdgeGap; p.startNewSubPath (x + textX + textW, y); p.lineTo (x + w - cs, y); p.addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); p.lineTo (x + w, y + h - cs); p.addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); p.lineTo (x + cs, y + h); p.addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); p.lineTo (x, y + cs); p.addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f); p.lineTo (x + textX, y); g.setColour (textColour); g.strokePath (p, PathStrokeType (2.0f)); g.setColour (textColour); g.setFont (f); g.drawText (text, roundFloatToInt (x + textX), 0, roundFloatToInt (textW), roundFloatToInt (textH), Justification::centred, true); }
AffineTransform Path::getTransformToScaleToFit (const float x, const float y, const float w, const float h, const bool preserveProportions, Justification justification) const { Rectangle<float> boundsRect (getBounds()); if (preserveProportions) { if (w <= 0 || h <= 0 || boundsRect.isEmpty()) return AffineTransform::identity; float newW, newH; const float srcRatio = boundsRect.getHeight() / boundsRect.getWidth(); if (srcRatio > h / w) { newW = h / srcRatio; newH = h; } else { newW = w; newH = w * srcRatio; } float newXCentre = x; float newYCentre = y; if (justification.testFlags (Justification::left)) newXCentre += newW * 0.5f; else if (justification.testFlags (Justification::right)) newXCentre += w - newW * 0.5f; else newXCentre += w * 0.5f; if (justification.testFlags (Justification::top)) newYCentre += newH * 0.5f; else if (justification.testFlags (Justification::bottom)) newYCentre += h - newH * 0.5f; else newYCentre += h * 0.5f; return AffineTransform::translation (boundsRect.getWidth() * -0.5f - boundsRect.getX(), boundsRect.getHeight() * -0.5f - boundsRect.getY()) .scaled (newW / boundsRect.getWidth(), newH / boundsRect.getHeight()) .translated (newXCentre, newYCentre); } else { return AffineTransform::translation (-boundsRect.getX(), -boundsRect.getY()) .scaled (w / boundsRect.getWidth(), h / boundsRect.getHeight()) .translated (x, y); } }
TRef<IObject> Apply(ObjectStack& stack) { TRef<StringValue> pstring; CastTo(pstring, (IObject*)stack.Pop()); TRef<ColorValue> pcolor; CastTo(pcolor, (IObject*)stack.Pop()); TRef<PointValue> ppointSize; WinPoint ptSize; if (stack.GetCount() > 0) { CastTo(ppointSize, (IObject*)stack.Pop()); ptSize = WinPoint( (int)ppointSize->GetValue().X(), (int)ppointSize->GetValue().Y() ); } Justification justification = JustifyLeft(); if (stack.GetCount() > 0) { TRef<Number> pjustify = Number::Cast((IObject*)stack.Pop()); justification.SetWord((DWORD)pjustify->GetValue()); } TRef<IEngineFont> pfont = TrekResources::SmallFont(); if (stack.GetCount() > 0) { TRef<FontValue> pfontLocal; CastTo(pfontLocal, (IObject*)stack.Pop()); pfont = pfontLocal->GetValue(); } bool bRightClip = false; if (stack.GetCount() > 0) bRightClip = GetBoolean((IObject*)stack.Pop()); TRef<StringValuePane> ppane; if (ppointSize) { if (bRightClip) justification = JustifyLeftClipRight(); ppane = new StringValuePane(pstring, pcolor, pfont, ptSize, justification); } else ppane = new StringValuePane(pstring, pcolor, pfont); return (Pane*)ppane; }
void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f, float x, float y, float width, float height, Justification layout) { GlyphArrangement ga; ga.addJustifiedText (f, text, x, y, width, layout); const Rectangle<float> bb (ga.getBoundingBox (0, -1, false)); float dy = y - bb.getY(); if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight()); ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); glyphs.addArray (ga.glyphs); }
//============================================================================== void GroupComponent::setTextLabelPosition (const Justification& newJustification) { if (justification.getFlags() != newJustification.getFlags()) { justification = newJustification; repaint(); } }
void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, const float x, const float y, const float width, const float height, const Justification& justification) { jassert (num >= 0 && startIndex >= 0); if (glyphs.size() > 0 && num > 0) { const Rectangle<float> bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified | Justification::horizontallyCentred))); float deltaX = 0.0f; if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX(); else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX(); else if (justification.testFlags (Justification::right)) deltaX = (x + width) - bb.getRight(); else deltaX = x - bb.getX(); float deltaY = 0.0f; if (justification.testFlags (Justification::top)) deltaY = y - bb.getY(); else if (justification.testFlags (Justification::bottom)) deltaY = (y + height) - bb.getBottom(); else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY(); moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); if (justification.testFlags (Justification::horizontallyJustified)) { int lineStart = 0; float baseY = glyphs.getUnchecked (startIndex)->getBaselineY(); int i; for (i = 0; i < num; ++i) { const float glyphY = glyphs.getUnchecked (startIndex + i)->getBaselineY(); if (glyphY != baseY) { spreadOutLine (startIndex + lineStart, i - lineStart, width); lineStart = i; baseY = glyphY; } } if (i > lineStart) spreadOutLine (startIndex + lineStart, i - lineStart, width); } } }
void FilterChart::drawText (Graphics &g, const Point<int> ptOrigin, const String text, Justification just) { const Font& font = g.getCurrentFont(); const int w = font.getStringWidth(text); int x, y; if (just.getOnlyHorizontalFlags() & Justification::right) x = ptOrigin.getX() - w; else x = ptOrigin.getX(); if (just.getOnlyVerticalFlags() & Justification::top) y = int (ptOrigin.getY() + font.getAscent() + 0.5); else y = ptOrigin.getY(); g.drawSingleLineText (text, x, y); }
static Rectangle<int> justified (Justification justification, const Rectangle<int>& space, const Rectangle<int>& item) { int itemX = item.getX(); int itemY = item.getY(); int itemW = item.getWidth(); int itemH = item.getHeight(); justification.applyToRectangle (itemX, itemY, itemW, itemH, space.getX(), space.getY(), space.getWidth(), space.getHeight()); return Rectangle<int> (itemX, itemY, itemW, itemH); }
void DrawableText::ValueTreeWrapper::setJustification (const Justification& newJustification, UndoManager* undoManager) { state.setProperty (justification, newJustification.getFlags(), undoManager); }
void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, float x, float y, float width, float height, int maximumLines, float lineWidth, Justification layout, float minimumHorizontalScale) { const int length = text.length(); const int originalStartIndex = startIndex; int numLines = 1; if (length <= 12 && ! text.containsAnyOf (" -\t\r\n")) maximumLines = 1; maximumLines = jmin (maximumLines, length); while (numLines < maximumLines) { ++numLines; const float newFontHeight = height / (float) numLines; if (newFontHeight < font.getHeight()) { font.setHeight (jmax (8.0f, newFontHeight)); removeRangeOfGlyphs (startIndex, -1); addLineOfText (font, text, x, y); lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() - glyphs.getReference (startIndex).getLeft(); } // Try to estimate the point at which there are enough lines to fit the text, // allowing for unevenness in the lengths due to differently sized words. const float lineLengthUnevennessAllowance = 80.0f; if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f) break; } if (numLines < 1) numLines = 1; float lineY = y; float widthPerLine = lineWidth / numLines; for (int line = 0; line < numLines; ++line) { int i = startIndex; float lineStartX = glyphs.getReference (startIndex).getLeft(); if (line == numLines - 1) { widthPerLine = width; i = glyphs.size(); } else { while (i < glyphs.size()) { lineWidth = (glyphs.getReference (i).getRight() - lineStartX); if (lineWidth > widthPerLine) { // got to a point where the line's too long, so skip forward to find a // good place to break it.. const int searchStartIndex = i; while (i < glyphs.size()) { if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width) { if (glyphs.getReference (i).isWhitespace() || glyphs.getReference (i).getCharacter() == '-') { ++i; break; } } else { // can't find a suitable break, so try looking backwards.. i = searchStartIndex; for (int back = 1; back < jmin (7, i - startIndex - 1); ++back) { if (glyphs.getReference (i - back).isWhitespace() || glyphs.getReference (i - back).getCharacter() == '-') { i -= back - 1; break; } } break; } ++i; } break; } ++i; } int wsStart = i; while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace()) --wsStart; int wsEnd = i; while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace()) ++wsEnd; removeRangeOfGlyphs (wsStart, wsEnd - wsStart); i = jmax (wsStart, startIndex + 1); } i -= fitLineIntoSpace (startIndex, i - startIndex, x, lineY, width, font.getHeight(), font, layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, minimumHorizontalScale); startIndex = i; lineY += font.getHeight(); if (startIndex >= glyphs.size()) break; } justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); }
void GlyphArrangement::addJustifiedText (const Font& font, const String& text, float x, float y, const float maxLineWidth, Justification horizontalLayout) { int lineStartIndex = glyphs.size(); addLineOfText (font, text, x, y); const float originalY = y; while (lineStartIndex < glyphs.size()) { int i = lineStartIndex; if (glyphs.getReference(i).getCharacter() != '\n' && glyphs.getReference(i).getCharacter() != '\r') ++i; const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth; int lastWordBreakIndex = -1; while (i < glyphs.size()) { const PositionedGlyph& pg = glyphs.getReference (i); const juce_wchar c = pg.getCharacter(); if (c == '\r' || c == '\n') { ++i; if (c == '\r' && i < glyphs.size() && glyphs.getReference(i).getCharacter() == '\n') ++i; break; } if (pg.isWhitespace()) { lastWordBreakIndex = i + 1; } else if (pg.getRight() - 0.0001f >= lineMaxX) { if (lastWordBreakIndex >= 0) i = lastWordBreakIndex; break; } ++i; } const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft(); float currentLineEndX = currentLineStartX; for (int j = i; --j >= lineStartIndex;) { if (! glyphs.getReference (j).isWhitespace()) { currentLineEndX = glyphs.getReference (j).getRight(); break; } } float deltaX = 0.0f; if (horizontalLayout.testFlags (Justification::horizontallyJustified)) spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth); else if (horizontalLayout.testFlags (Justification::horizontallyCentred)) deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f; else if (horizontalLayout.testFlags (Justification::right)) deltaX = maxLineWidth - (currentLineEndX - currentLineStartX); moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, x + deltaX - currentLineStartX, y - originalY); lineStartIndex = i; y += font.getHeight(); } }
void GlyphArrangement::addFittedText (const Font& f, const String& text, const float x, const float y, const float width, const float height, const Justification& layout, int maximumLines, const float minimumHorizontalScale) { // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); if (text.containsAnyOf ("\r\n")) { GlyphArrangement ga; ga.addJustifiedText (f, text, x, y, width, layout); const Rectangle<float> bb (ga.getBoundingBox (0, -1, false)); float dy = y - bb.getY(); if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; else if (layout.testFlags (Justification::bottom)) dy += height - bb.getHeight(); ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); glyphs.ensureStorageAllocated (glyphs.size() + ga.glyphs.size()); for (int i = 0; i < ga.glyphs.size(); ++i) glyphs.add (ga.glyphs.getUnchecked (i)); ga.glyphs.clear (false); return; } int startIndex = glyphs.size(); addLineOfText (f, text.trim(), x, y); if (glyphs.size() > startIndex) { float lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - glyphs.getUnchecked (startIndex)->getLeft(); if (lineWidth <= 0) return; if (lineWidth * minimumHorizontalScale < width) { if (lineWidth > width) stretchRangeOfGlyphs (startIndex, glyphs.size() - startIndex, width / lineWidth); justifyGlyphs (startIndex, glyphs.size() - startIndex, x, y, width, height, layout); } else if (maximumLines <= 1) { fitLineIntoSpace (startIndex, glyphs.size() - startIndex, x, y, width, height, f, layout, minimumHorizontalScale); } else { Font font (f); String txt (text.trim()); const int length = txt.length(); const int originalStartIndex = startIndex; int numLines = 1; if (length <= 12 && ! txt.containsAnyOf (" -\t\r\n")) maximumLines = 1; maximumLines = jmin (maximumLines, length); while (numLines < maximumLines) { ++numLines; const float newFontHeight = height / (float) numLines; if (newFontHeight < font.getHeight()) { font.setHeight (jmax (8.0f, newFontHeight)); removeRangeOfGlyphs (startIndex, -1); addLineOfText (font, txt, x, y); lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - glyphs.getUnchecked (startIndex)->getLeft(); } if (numLines > lineWidth / width || newFontHeight < 8.0f) break; } if (numLines < 1) numLines = 1; float lineY = y; float widthPerLine = lineWidth / numLines; int lastLineStartIndex = 0; for (int line = 0; line < numLines; ++line) { int i = startIndex; lastLineStartIndex = i; float lineStartX = glyphs.getUnchecked (startIndex)->getLeft(); if (line == numLines - 1) { widthPerLine = width; i = glyphs.size(); } else { while (i < glyphs.size()) { lineWidth = (glyphs.getUnchecked (i)->getRight() - lineStartX); if (lineWidth > widthPerLine) { // got to a point where the line's too long, so skip forward to find a // good place to break it.. const int searchStartIndex = i; while (i < glyphs.size()) { if ((glyphs.getUnchecked (i)->getRight() - lineStartX) * minimumHorizontalScale < width) { if (glyphs.getUnchecked (i)->isWhitespace() || glyphs.getUnchecked (i)->getCharacter() == '-') { ++i; break; } } else { // can't find a suitable break, so try looking backwards.. i = searchStartIndex; for (int back = 1; back < jmin (5, i - startIndex - 1); ++back) { if (glyphs.getUnchecked (i - back)->isWhitespace() || glyphs.getUnchecked (i - back)->getCharacter() == '-') { i -= back - 1; break; } } break; } ++i; } break; } ++i; } int wsStart = i; while (wsStart > 0 && glyphs.getUnchecked (wsStart - 1)->isWhitespace()) --wsStart; int wsEnd = i; while (wsEnd < glyphs.size() && glyphs.getUnchecked (wsEnd)->isWhitespace()) ++wsEnd; removeRangeOfGlyphs (wsStart, wsEnd - wsStart); i = jmax (wsStart, startIndex + 1); } i -= fitLineIntoSpace (startIndex, i - startIndex, x, lineY, width, font.getHeight(), font, layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, minimumHorizontalScale); startIndex = i; lineY += font.getHeight(); if (startIndex >= glyphs.size()) break; } justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); } } }