diff --git a/amalgamation/juce_amalgamated_template.cpp b/amalgamation/juce_amalgamated_template.cpp index 2628e59bff..649ef824e1 100644 --- a/amalgamation/juce_amalgamated_template.cpp +++ b/amalgamation/juce_amalgamated_template.cpp @@ -319,6 +319,7 @@ #include "../src/gui/components/windows/juce_ThreadWithProgressWindow.cpp" #include "../src/gui/components/windows/juce_TooltipWindow.cpp" #include "../src/gui/components/windows/juce_TopLevelWindow.cpp" + #include "../src/gui/graphics/geometry/juce_RelativeCoordinate.cpp" #endif #if JUCE_BUILD_MISC // (put these in misc to balance the file sizes and avoid problems in iphone build) @@ -352,7 +353,6 @@ #include "../src/gui/graphics/geometry/juce_PathStrokeType.cpp" #include "../src/gui/graphics/geometry/juce_PositionedRectangle.cpp" #include "../src/gui/graphics/geometry/juce_RectangleList.cpp" - #include "../src/gui/graphics/geometry/juce_RelativeCoordinate.cpp" #include "../src/gui/graphics/imaging/juce_Image.cpp" #include "../src/gui/graphics/imaging/juce_ImageCache.cpp" #include "../src/gui/graphics/imaging/juce_ImageConvolutionKernel.cpp" diff --git a/extras/Jucer (experimental)/Source/Application/jucer_Application.h b/extras/Jucer (experimental)/Source/Application/jucer_Application.h index 78d77a088b..3236bbde09 100644 --- a/extras/Jucer (experimental)/Source/Application/jucer_Application.h +++ b/extras/Jucer (experimental)/Source/Application/jucer_Application.h @@ -292,7 +292,7 @@ public: commands.addArray (ids, numElementsInArray (ids)); } - void getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) + void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) { switch (commandID) { diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 0f814ea39b..a2b744951b 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -37826,6 +37826,10 @@ MessageManager::~MessageManager() throw() doPlatformSpecificShutdown(); + // If you hit this assertion, then you've probably leaked a Component or some other + // kind of MessageListener object... + jassert (messageListeners.size() == 0); + jassert (instance == this); instance = 0; // do this last in case this instance is still needed by doPlatformSpecificShutdown() } @@ -78799,15445 +78803,15445 @@ TopLevelWindow* TopLevelWindow::getActiveTopLevelWindow() throw() END_JUCE_NAMESPACE /*** End of inlined file: juce_TopLevelWindow.cpp ***/ -#endif - -#if JUCE_BUILD_MISC // (put these in misc to balance the file sizes and avoid problems in iphone build) -/*** Start of inlined file: juce_Colour.cpp ***/ +/*** Start of inlined file: juce_RelativeCoordinate.cpp ***/ BEGIN_JUCE_NAMESPACE -namespace ColourHelpers +namespace RelativeCoordinateHelpers { - static uint8 floatAlphaToInt (const float alpha) throw() + static bool isOrigin (const String& name) { - return (uint8) jlimit (0, 0xff, roundToInt (alpha * 255.0f)); + return name.isEmpty() + || name == RelativeCoordinate::Strings::parentLeft + || name == RelativeCoordinate::Strings::parentTop; } - static void convertHSBtoRGB (float h, float s, float v, - uint8& r, uint8& g, uint8& b) throw() + static const String getExtentAnchorName (const bool isHorizontal) throw() { - v = jlimit (0.0f, 1.0f, v); - v *= 255.0f; - const uint8 intV = (uint8) roundToInt (v); + return isHorizontal ? RelativeCoordinate::Strings::parentRight + : RelativeCoordinate::Strings::parentBottom; + } - if (s <= 0) + static const String getObjectName (const String& fullName) + { + return fullName.upToFirstOccurrenceOf (".", false, false); + } + + static const String getEdgeName (const String& fullName) + { + return fullName.fromFirstOccurrenceOf (".", false, false); + } + + static const RelativeCoordinate findCoordinate (const String& name, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) + { + return nameFinder != 0 ? nameFinder->findNamedCoordinate (getObjectName (name), getEdgeName (name)) + : RelativeCoordinate(); + } + + struct RecursionException : public std::runtime_error + { + RecursionException() : std::runtime_error ("Recursive RelativeCoordinate expression") { - r = intV; - g = intV; - b = intV; } - else + }; + + static void skipWhitespace (const juce_wchar* const s, int& i) + { + while (CharacterFunctions::isWhitespace (s[i])) + ++i; + } + + static void skipComma (const juce_wchar* const s, int& i) + { + skipWhitespace (s, i); + if (s[i] == ',') + ++i; + } + + static const String readAnchorName (const juce_wchar* const s, int& i) + { + skipWhitespace (s, i); + + if (CharacterFunctions::isLetter (s[i]) || s[i] == '_') { - s = jmin (1.0f, s); - h = jlimit (0.0f, 1.0f, h); - h = (h - std::floor (h)) * 6.0f + 0.00001f; // need a small adjustment to compensate for rounding errors - const float f = h - std::floor (h); + int start = i; + while (CharacterFunctions::isLetterOrDigit (s[i]) || s[i] == '_' || s[i] == '.') + ++i; - const uint8 x = (uint8) roundToInt (v * (1.0f - s)); - const float y = v * (1.0f - s * f); - const float z = v * (1.0f - (s * (1.0f - f))); + return String (s + start, i - start); + } - if (h < 1.0f) - { - r = intV; - g = (uint8) roundToInt (z); - b = x; - } - else if (h < 2.0f) - { - r = (uint8) roundToInt (y); - g = intV; - b = x; - } - else if (h < 3.0f) - { - r = x; - g = intV; - b = (uint8) roundToInt (z); - } - else if (h < 4.0f) - { - r = x; - g = (uint8) roundToInt (y); - b = intV; - } - else if (h < 5.0f) - { - r = (uint8) roundToInt (z); - g = x; - b = intV; - } - else if (h < 6.0f) - { - r = intV; - g = x; - b = (uint8) roundToInt (y); - } - else + return String::empty; + } + + static double readNumber (const juce_wchar* const s, int& i) + { + skipWhitespace (s, i); + + int start = i; + if (CharacterFunctions::isDigit (s[i]) || s[i] == '.' || s[i] == '-') + ++i; + + while (CharacterFunctions::isDigit (s[i]) || s[i] == '.') + ++i; + + if ((s[i] == 'e' || s[i] == 'E') + && (CharacterFunctions::isDigit (s[i + 1]) + || s[i + 1] == '-' + || s[i + 1] == '+')) + { + i += 2; + + while (CharacterFunctions::isDigit (s[i])) + ++i; + } + + const double value = String (s + start, i - start).getDoubleValue(); + while (CharacterFunctions::isWhitespace (s[i]) || s[i] == ',') + ++i; + + return value; + } + + static const RelativeCoordinate readNextCoordinate (const juce_wchar* const s, int& i, const bool isHorizontal) + { + String anchor1 (readAnchorName (s, i)); + double value = 0; + + if (anchor1.isNotEmpty()) + { + skipWhitespace (s, i); + + if (s[i] == '+') + value = readNumber (s, ++i); + else if (s[i] == '-') + value = -readNumber (s, ++i); + + return RelativeCoordinate (value, anchor1); + } + else + { + value = readNumber (s, i); + skipWhitespace (s, i); + + if (s[i] == '%') { - r = 0; - g = 0; - b = 0; + value /= 100.0; + skipWhitespace (s, ++i); + String anchor2; + + if (s[i] == '*') + { + anchor1 = readAnchorName (s, ++i); + + skipWhitespace (s, i); + + if (s[i] == '-' && s[i + 1] == '>') + { + i += 2; + anchor2 = readAnchorName (s, i); + } + else + { + anchor2 = anchor1; + anchor1 = String::empty; + } + } + else + { + anchor1 = String::empty; + anchor2 = getExtentAnchorName (isHorizontal); + } + + return RelativeCoordinate (value, anchor1, anchor2); } + + return RelativeCoordinate (value); } } -} -Colour::Colour() throw() - : argb (0) -{ -} + static const String limitedAccuracyString (const double n) + { + if (! (n < -0.001 || n > 0.001)) // to detect NaN and inf as well as for rounding + return "0"; -Colour::Colour (const Colour& other) throw() - : argb (other.argb) -{ + return String (n, 3).trimCharactersAtEnd ("0").trimCharactersAtEnd ("."); + } } -Colour& Colour::operator= (const Colour& other) throw() +const String RelativeCoordinate::Strings::parent ("parent"); +const String RelativeCoordinate::Strings::left ("left"); +const String RelativeCoordinate::Strings::right ("right"); +const String RelativeCoordinate::Strings::top ("top"); +const String RelativeCoordinate::Strings::bottom ("bottom"); +const String RelativeCoordinate::Strings::parentLeft ("parent.left"); +const String RelativeCoordinate::Strings::parentTop ("parent.top"); +const String RelativeCoordinate::Strings::parentRight ("parent.right"); +const String RelativeCoordinate::Strings::parentBottom ("parent.bottom"); + +RelativeCoordinate::RelativeCoordinate() + : value (0) { - argb = other.argb; - return *this; } -bool Colour::operator== (const Colour& other) const throw() +RelativeCoordinate::RelativeCoordinate (const double absoluteDistanceFromOrigin) + : value (absoluteDistanceFromOrigin) { - return argb.getARGB() == other.argb.getARGB(); } -bool Colour::operator!= (const Colour& other) const throw() +RelativeCoordinate::RelativeCoordinate (const double absoluteDistance, const String& source) + : anchor1 (source.trim()), + value (absoluteDistance) { - return argb.getARGB() != other.argb.getARGB(); } -Colour::Colour (const uint32 argb_) throw() - : argb (argb_) +RelativeCoordinate::RelativeCoordinate (const double relativeProportion, const String& pos1, const String& pos2) + : anchor1 (pos1.trim()), + anchor2 (pos2.trim()), + value (relativeProportion) { } -Colour::Colour (const uint8 red, - const uint8 green, - const uint8 blue) throw() +RelativeCoordinate::RelativeCoordinate (const String& s, const bool isHorizontal) + : value (0) { - argb.setARGB (0xff, red, green, blue); + int i = 0; + *this = RelativeCoordinateHelpers::readNextCoordinate (s, i, isHorizontal); } -const Colour Colour::fromRGB (const uint8 red, - const uint8 green, - const uint8 blue) throw() +RelativeCoordinate::~RelativeCoordinate() { - return Colour (red, green, blue); } -Colour::Colour (const uint8 red, - const uint8 green, - const uint8 blue, - const uint8 alpha) throw() +bool RelativeCoordinate::operator== (const RelativeCoordinate& other) const throw() { - argb.setARGB (alpha, red, green, blue); + return value == other.value && anchor1 == other.anchor1 && anchor2 == other.anchor2; } -const Colour Colour::fromRGBA (const uint8 red, - const uint8 green, - const uint8 blue, - const uint8 alpha) throw() +bool RelativeCoordinate::operator!= (const RelativeCoordinate& other) const throw() { - return Colour (red, green, blue, alpha); + return ! operator== (other); } -Colour::Colour (const uint8 red, - const uint8 green, - const uint8 blue, - const float alpha) throw() +const RelativeCoordinate RelativeCoordinate::getAnchorCoordinate1() const { - argb.setARGB (ColourHelpers::floatAlphaToInt (alpha), red, green, blue); + return RelativeCoordinate (0.0, anchor1); } -const Colour Colour::fromRGBAFloat (const uint8 red, - const uint8 green, - const uint8 blue, - const float alpha) throw() +const RelativeCoordinate RelativeCoordinate::getAnchorCoordinate2() const { - return Colour (red, green, blue, alpha); + return RelativeCoordinate (0.0, anchor2); } -Colour::Colour (const float hue, - const float saturation, - const float brightness, - const float alpha) throw() +double RelativeCoordinate::resolveAnchor (const String& anchorName, const NamedCoordinateFinder* nameFinder, int recursionCounter) { - uint8 r = getRed(), g = getGreen(), b = getBlue(); - ColourHelpers::convertHSBtoRGB (hue, saturation, brightness, r, g, b); + if (RelativeCoordinateHelpers::isOrigin (anchorName)) + return 0.0; - argb.setARGB (ColourHelpers::floatAlphaToInt (alpha), r, g, b); + return RelativeCoordinateHelpers::findCoordinate (anchorName, nameFinder).resolve (nameFinder, recursionCounter + 1); } -const Colour Colour::fromHSV (const float hue, - const float saturation, - const float brightness, - const float alpha) throw() +double RelativeCoordinate::resolve (const NamedCoordinateFinder* nameFinder, int recursionCounter) const { - return Colour (hue, saturation, brightness, alpha); -} + if (recursionCounter > 150) + { + jassertfalse + throw RelativeCoordinateHelpers::RecursionException(); + } -Colour::Colour (const float hue, - const float saturation, - const float brightness, - const uint8 alpha) throw() -{ - uint8 r = getRed(), g = getGreen(), b = getBlue(); - ColourHelpers::convertHSBtoRGB (hue, saturation, brightness, r, g, b); + const double pos1 = resolveAnchor (anchor1, nameFinder, recursionCounter); - argb.setARGB (alpha, r, g, b); + return isProportional() ? pos1 + (resolveAnchor (anchor2, nameFinder, recursionCounter) - pos1) * value + : pos1 + value; } -Colour::~Colour() throw() +double RelativeCoordinate::resolve (const NamedCoordinateFinder* nameFinder) const { -} + try + { + return resolve (nameFinder, 0); + } + catch (RelativeCoordinateHelpers::RecursionException&) + {} -const PixelARGB Colour::getPixelARGB() const throw() -{ - PixelARGB p (argb); - p.premultiply(); - return p; + return 0.0; } -uint32 Colour::getARGB() const throw() +bool RelativeCoordinate::isRecursive (const NamedCoordinateFinder* nameFinder) const { - return argb.getARGB(); -} + try + { + (void) resolve (nameFinder, 0); + } + catch (RelativeCoordinateHelpers::RecursionException&) + { + return true; + } -bool Colour::isTransparent() const throw() -{ - return getAlpha() == 0; + return false; } -bool Colour::isOpaque() const throw() +void RelativeCoordinate::moveToAbsolute (double newPos, const NamedCoordinateFinder* nameFinder) { - return getAlpha() == 0xff; + try + { + const double pos1 = resolveAnchor (anchor1, nameFinder, 0); + + if (isProportional()) + { + const double size = resolveAnchor (anchor2, nameFinder, 0) - pos1; + + if (size != 0) + value = (newPos - pos1) / size; + } + else + { + value = newPos - pos1; + } + } + catch (RelativeCoordinateHelpers::RecursionException&) + {} } -const Colour Colour::withAlpha (const uint8 newAlpha) const throw() +void RelativeCoordinate::toggleProportionality (const NamedCoordinateFinder* nameFinder, + const String& proportionalAnchor1, const String& proportionalAnchor2) { - PixelARGB newCol (argb); - newCol.setAlpha (newAlpha); - return Colour (newCol.getARGB()); + const double oldValue = resolve (nameFinder); + + anchor1 = proportionalAnchor1; + anchor2 = isProportional() ? String::empty : proportionalAnchor2; + + moveToAbsolute (oldValue, nameFinder); } -const Colour Colour::withAlpha (const float newAlpha) const throw() +bool RelativeCoordinate::references (const String& coordName, const NamedCoordinateFinder* nameFinder) const { - jassert (newAlpha >= 0 && newAlpha <= 1.0f); + using namespace RelativeCoordinateHelpers; - PixelARGB newCol (argb); - newCol.setAlpha (ColourHelpers::floatAlphaToInt (newAlpha)); - return Colour (newCol.getARGB()); + if (isOrigin (anchor1) && ! isProportional()) + return isOrigin (coordName); + + return anchor1 == coordName + || anchor2 == coordName + || findCoordinate (anchor1, nameFinder).references (coordName, nameFinder) + || (isProportional() && findCoordinate (anchor2, nameFinder).references (coordName, nameFinder)); } -const Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const throw() +bool RelativeCoordinate::isDynamic() const { - jassert (alphaMultiplier >= 0); - - PixelARGB newCol (argb); - newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); - return Colour (newCol.getARGB()); + return anchor2.isNotEmpty() || ! RelativeCoordinateHelpers::isOrigin (anchor1); } -const Colour Colour::overlaidWith (const Colour& src) const throw() +const String RelativeCoordinate::toString() const { - const int destAlpha = getAlpha(); + using namespace RelativeCoordinateHelpers; - if (destAlpha > 0) + if (isProportional()) { - const int invA = 0xff - (int) src.getAlpha(); - const int resA = 0xff - (((0xff - destAlpha) * invA) >> 8); + const String percent (limitedAccuracyString (value * 100.0)); - if (resA > 0) + if (isOrigin (anchor1)) { - const int da = (invA * destAlpha) / resA; - - return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), - (uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), - (uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), - (uint8) resA); + if (anchor2 == Strings::parentRight || anchor2 == Strings::parentBottom) + return percent + "%"; + else + return percent + "% * " + anchor2; } - - return *this; + else + return percent + "% * " + anchor1 + " -> " + anchor2; } else { - return src; + if (isOrigin (anchor1)) + return limitedAccuracyString (value); + else if (value > 0) + return anchor1 + " + " + limitedAccuracyString (value); + else if (value < 0) + return anchor1 + " - " + limitedAccuracyString (-value); + else + return anchor1; } } -const Colour Colour::interpolatedWith (const Colour& other, float proportionOfOther) const throw() +const double RelativeCoordinate::getEditableNumber() const { - if (proportionOfOther <= 0) - return *this; - - if (proportionOfOther >= 1.0f) - return other; - - PixelARGB c1 (getPixelARGB()); - const PixelARGB c2 (other.getPixelARGB()); - c1.tween (c2, roundToInt (proportionOfOther * 255.0f)); - c1.unpremultiply(); - - return Colour (c1.getARGB()); + return isProportional() ? value * 100.0 : value; } -float Colour::getFloatRed() const throw() +void RelativeCoordinate::setEditableNumber (const double newValue) { - return getRed() / 255.0f; + value = isProportional() ? newValue / 100.0 : newValue; } -float Colour::getFloatGreen() const throw() +const String RelativeCoordinate::getAnchorName1 (const String& returnValueIfOrigin) const { - return getGreen() / 255.0f; + return RelativeCoordinateHelpers::isOrigin (anchor1) ? returnValueIfOrigin : anchor1; } -float Colour::getFloatBlue() const throw() +const String RelativeCoordinate::getAnchorName2 (const String& returnValueIfOrigin) const { - return getBlue() / 255.0f; + return RelativeCoordinateHelpers::isOrigin (anchor2) ? returnValueIfOrigin : anchor2; } -float Colour::getFloatAlpha() const throw() +void RelativeCoordinate::changeAnchor1 (const String& newAnchorName, const NamedCoordinateFinder* nameFinder) { - return getAlpha() / 255.0f; + jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_.")); + + const double oldValue = resolve (nameFinder); + anchor1 = RelativeCoordinateHelpers::isOrigin (newAnchorName) ? String::empty : newAnchorName; + moveToAbsolute (oldValue, nameFinder); } -void Colour::getHSB (float& h, float& s, float& v) const throw() +void RelativeCoordinate::changeAnchor2 (const String& newAnchorName, const NamedCoordinateFinder* nameFinder) { - const int r = getRed(); - const int g = getGreen(); - const int b = getBlue(); + jassert (isProportional()); + jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_.")); - const int hi = jmax (r, g, b); - const int lo = jmin (r, g, b); + const double oldValue = resolve (nameFinder); + anchor2 = RelativeCoordinateHelpers::isOrigin (newAnchorName) ? String::empty : newAnchorName; + moveToAbsolute (oldValue, nameFinder); +} - if (hi != 0) - { - s = (hi - lo) / (float) hi; +void RelativeCoordinate::renameAnchorIfUsed (const String& oldName, const String& newName, const NamedCoordinateFinder* nameFinder) +{ + using namespace RelativeCoordinateHelpers; + jassert (oldName.isNotEmpty()); + jassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); - if (s != 0) + if (newName.isEmpty()) + { + if (getObjectName (anchor1) == oldName + || getObjectName (anchor2) == oldName) { - const float invDiff = 1.0f / (hi - lo); - - const float red = (hi - r) * invDiff; - const float green = (hi - g) * invDiff; - const float blue = (hi - b) * invDiff; - - if (r == hi) - h = blue - green; - else if (g == hi) - h = 2.0f + red - blue; - else - h = 4.0f + green - red; - - h *= 1.0f / 6.0f; - - if (h < 0) - ++h; - } - else - { - h = 0; + value = resolve (nameFinder); + anchor1 = String::empty; + anchor2 = String::empty; } } else { - s = 0; - h = 0; - } + if (getObjectName (anchor1) == oldName) + anchor1 = newName + "." + getEdgeName (anchor1); - v = hi / 255.0f; + if (getObjectName (anchor2) == oldName) + anchor2 = newName + "." + getEdgeName (anchor2); + } } -float Colour::getHue() const throw() +RelativePoint::RelativePoint() { - float h, s, b; - getHSB (h, s, b); - return h; } -const Colour Colour::withHue (const float hue) const throw() +RelativePoint::RelativePoint (const Point& absolutePoint) + : x (absolutePoint.getX()), y (absolutePoint.getY()) { - float h, s, b; - getHSB (h, s, b); +} - return Colour (hue, s, b, getAlpha()); +RelativePoint::RelativePoint (const float x_, const float y_) + : x (x_), y (y_) +{ } -const Colour Colour::withRotatedHue (const float amountToRotate) const throw() +RelativePoint::RelativePoint (const RelativeCoordinate& x_, const RelativeCoordinate& y_) + : x (x_), y (y_) { - float h, s, b; - getHSB (h, s, b); +} - h += amountToRotate; - h -= std::floor (h); +RelativePoint::RelativePoint (const String& s) +{ + int i = 0; + x = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); + RelativeCoordinateHelpers::skipComma (s, i); + y = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); +} - return Colour (h, s, b, getAlpha()); +bool RelativePoint::operator== (const RelativePoint& other) const throw() +{ + return x == other.x && y == other.y; } -float Colour::getSaturation() const throw() +bool RelativePoint::operator!= (const RelativePoint& other) const throw() { - float h, s, b; - getHSB (h, s, b); - return s; + return ! operator== (other); } -const Colour Colour::withSaturation (const float saturation) const throw() +const Point RelativePoint::resolve (const RelativeCoordinate::NamedCoordinateFinder* nameFinder) const { - float h, s, b; - getHSB (h, s, b); + return Point ((float) x.resolve (nameFinder), + (float) y.resolve (nameFinder)); +} - return Colour (h, saturation, b, getAlpha()); +void RelativePoint::moveToAbsolute (const Point& newPos, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) +{ + x.moveToAbsolute (newPos.getX(), nameFinder); + y.moveToAbsolute (newPos.getY(), nameFinder); } -const Colour Colour::withMultipliedSaturation (const float amount) const throw() +const String RelativePoint::toString() const { - float h, s, b; - getHSB (h, s, b); + return x.toString() + ", " + y.toString(); +} - return Colour (h, jmin (1.0f, s * amount), b, getAlpha()); +void RelativePoint::renameAnchorIfUsed (const String& oldName, const String& newName, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) +{ + x.renameAnchorIfUsed (oldName, newName, nameFinder); + y.renameAnchorIfUsed (oldName, newName, nameFinder); } -float Colour::getBrightness() const throw() +bool RelativePoint::isDynamic() const { - float h, s, b; - getHSB (h, s, b); - return b; + return x.isDynamic() || y.isDynamic(); } -const Colour Colour::withBrightness (const float brightness) const throw() +RelativeRectangle::RelativeRectangle() { - float h, s, b; - getHSB (h, s, b); +} - return Colour (h, s, brightness, getAlpha()); +RelativeRectangle::RelativeRectangle (const RelativeCoordinate& left_, const RelativeCoordinate& right_, + const RelativeCoordinate& top_, const RelativeCoordinate& bottom_) + : left (left_), right (right_), top (top_), bottom (bottom_) +{ } -const Colour Colour::withMultipliedBrightness (const float amount) const throw() +RelativeRectangle::RelativeRectangle (const Rectangle& rect, const String& componentName) + : left (rect.getX()), + right (rect.getWidth(), componentName + "." + RelativeCoordinate::Strings::left), + top (rect.getY()), + bottom (rect.getHeight(), componentName + "." + RelativeCoordinate::Strings::top) { - float h, s, b; - getHSB (h, s, b); +} - b *= amount; +RelativeRectangle::RelativeRectangle (const String& s) +{ + int i = 0; + left = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); + RelativeCoordinateHelpers::skipComma (s, i); + top = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); + RelativeCoordinateHelpers::skipComma (s, i); + right = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); + RelativeCoordinateHelpers::skipComma (s, i); + bottom = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); +} - if (b > 1.0f) - b = 1.0f; +bool RelativeRectangle::operator== (const RelativeRectangle& other) const throw() +{ + return left == other.left && top == other.top && right == other.right && bottom == other.bottom; +} - return Colour (h, s, b, getAlpha()); +bool RelativeRectangle::operator!= (const RelativeRectangle& other) const throw() +{ + return ! operator== (other); } -const Colour Colour::brighter (float amount) const throw() +const Rectangle RelativeRectangle::resolve (const RelativeCoordinate::NamedCoordinateFinder* nameFinder) const { - amount = 1.0f / (1.0f + amount); + const double l = left.resolve (nameFinder); + const double r = right.resolve (nameFinder); + const double t = top.resolve (nameFinder); + const double b = bottom.resolve (nameFinder); - return Colour ((uint8) (255 - (amount * (255 - getRed()))), - (uint8) (255 - (amount * (255 - getGreen()))), - (uint8) (255 - (amount * (255 - getBlue()))), - getAlpha()); + return Rectangle ((float) l, (float) t, (float) (r - l), (float) (b - t)); } -const Colour Colour::darker (float amount) const throw() +void RelativeRectangle::moveToAbsolute (const Rectangle& newPos, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) { - amount = 1.0f / (1.0f + amount); + left.moveToAbsolute (newPos.getX(), nameFinder); + right.moveToAbsolute (newPos.getRight(), nameFinder); + top.moveToAbsolute (newPos.getY(), nameFinder); + bottom.moveToAbsolute (newPos.getBottom(), nameFinder); +} - return Colour ((uint8) (amount * getRed()), - (uint8) (amount * getGreen()), - (uint8) (amount * getBlue()), - getAlpha()); +const String RelativeRectangle::toString() const +{ + return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString(); } -const Colour Colour::greyLevel (const float brightness) throw() +void RelativeRectangle::renameAnchorIfUsed (const String& oldName, const String& newName, + const RelativeCoordinate::NamedCoordinateFinder* nameFinder) { - const uint8 level - = (uint8) jlimit (0x00, 0xff, roundToInt (brightness * 255.0f)); + left.renameAnchorIfUsed (oldName, newName, nameFinder); + right.renameAnchorIfUsed (oldName, newName, nameFinder); + top.renameAnchorIfUsed (oldName, newName, nameFinder); + bottom.renameAnchorIfUsed (oldName, newName, nameFinder); +} - return Colour (level, level, level); +RelativePointPath::RelativePointPath() + : usesNonZeroWinding (true), + containsDynamicPoints (false) +{ } -const Colour Colour::contrasting (const float amount) const throw() +RelativePointPath::RelativePointPath (const RelativePointPath& other) + : usesNonZeroWinding (true), + containsDynamicPoints (false) { - return overlaidWith ((((int) getRed() + (int) getGreen() + (int) getBlue() >= 3 * 128) - ? Colours::black - : Colours::white).withAlpha (amount)); + ValueTree state (DrawablePath::valueTreeType); + other.writeTo (state, 0); + parse (state); } -const Colour Colour::contrasting (const Colour& colour1, - const Colour& colour2) throw() +RelativePointPath::RelativePointPath (const ValueTree& drawable) + : usesNonZeroWinding (true), + containsDynamicPoints (false) { - const float b1 = colour1.getBrightness(); - const float b2 = colour2.getBrightness(); - float best = 0.0f; - float bestDist = 0.0f; + parse (drawable); +} - for (float i = 0.0f; i < 1.0f; i += 0.02f) - { - const float d1 = std::abs (i - b1); - const float d2 = std::abs (i - b2); - const float dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); +RelativePointPath::RelativePointPath (const Path& path) +{ + usesNonZeroWinding = path.isUsingNonZeroWinding(); - if (dist > bestDist) + Path::Iterator i (path); + + while (i.next()) + { + switch (i.elementType) { - best = i; - bestDist = dist; + case Path::Iterator::startNewSubPath: elements.add (new StartSubPath (RelativePoint (i.x1, i.y1))); break; + case Path::Iterator::lineTo: elements.add (new LineTo (RelativePoint (i.x1, i.y1))); break; + case Path::Iterator::quadraticTo: elements.add (new QuadraticTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2))); break; + case Path::Iterator::cubicTo: elements.add (new CubicTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2), RelativePoint (i.x3, i.y3))); break; + case Path::Iterator::closePath: elements.add (new CloseSubPath()); break; + default: jassertfalse; break; } } - - return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) - .withBrightness (best); } -const String Colour::toString() const +void RelativePointPath::writeTo (ValueTree state, UndoManager* undoManager) const { - return String::toHexString ((int) argb.getARGB()); -} + DrawablePath::ValueTreeWrapper wrapper (state); + wrapper.setUsesNonZeroWinding (usesNonZeroWinding, undoManager); -const Colour Colour::fromString (const String& encodedColourString) -{ - return Colour ((uint32) encodedColourString.getHexValue32()); + ValueTree pathTree (wrapper.getPathState()); + pathTree.removeAllChildren (undoManager); + + for (int i = 0; i < elements.size(); ++i) + pathTree.addChild (elements.getUnchecked(i)->createTree(), -1, undoManager); } -const String Colour::toDisplayString (const bool includeAlphaValue) const +void RelativePointPath::parse (const ValueTree& state) { - return String::toHexString ((int) (argb.getARGB() & (includeAlphaValue ? 0xffffffff : 0xffffff))) - .paddedLeft ('0', includeAlphaValue ? 8 : 6) - .toUpperCase(); -} + DrawablePath::ValueTreeWrapper wrapper (state); + usesNonZeroWinding = wrapper.usesNonZeroWinding(); + RelativePoint points[3]; -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Colour.cpp ***/ + const ValueTree pathTree (wrapper.getPathState()); + const int num = pathTree.getNumChildren(); + for (int i = 0; i < num; ++i) + { + const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); + const int numCps = e.getNumControlPoints(); + for (int j = 0; j < numCps; ++j) + { + points[j] = e.getControlPoint (j); + containsDynamicPoints = containsDynamicPoints || points[j].isDynamic(); + } + const Identifier type (e.getType()); -/*** Start of inlined file: juce_ColourGradient.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (type == DrawablePath::ValueTreeWrapper::Element::startSubPathElement) + elements.add (new StartSubPath (points[0])); + else if (type == DrawablePath::ValueTreeWrapper::Element::closeSubPathElement) + elements.add (new CloseSubPath()); + else if (type == DrawablePath::ValueTreeWrapper::Element::lineToElement) + elements.add (new LineTo (points[0])); + else if (type == DrawablePath::ValueTreeWrapper::Element::quadraticToElement) + elements.add (new QuadraticTo (points[0], points[1])); + else if (type == DrawablePath::ValueTreeWrapper::Element::cubicToElement) + elements.add (new CubicTo (points[0], points[1], points[2])); + else + jassertfalse; + } +} -ColourGradient::ColourGradient() throw() +RelativePointPath::~RelativePointPath() { -#if JUCE_DEBUG - point1.setX (987654.0f); -#endif } -ColourGradient::ColourGradient (const Colour& colour1, const float x1_, const float y1_, - const Colour& colour2, const float x2_, const float y2_, - const bool isRadial_) - : point1 (x1_, y1_), - point2 (x2_, y2_), - isRadial (isRadial_) +void RelativePointPath::swapWith (RelativePointPath& other) throw() { - colours.add (ColourPoint (0.0, colour1)); - colours.add (ColourPoint (1.0, colour2)); + elements.swapWithArray (other.elements); + swapVariables (usesNonZeroWinding, other.usesNonZeroWinding); } -ColourGradient::~ColourGradient() +void RelativePointPath::createPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) { + for (int i = 0; i < elements.size(); ++i) + elements.getUnchecked(i)->addToPath (path, coordFinder); } -bool ColourGradient::operator== (const ColourGradient& other) const throw() +bool RelativePointPath::containsAnyDynamicPoints() const { - return point1 == other.point1 && point2 == other.point2 - && isRadial == other.isRadial - && colours == other.colours; + return containsDynamicPoints; } -bool ColourGradient::operator!= (const ColourGradient& other) const throw() +RelativePointPath::ElementBase::ElementBase (const ElementType type_) : type (type_) { - return ! operator== (other); } -void ColourGradient::clearColours() +RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos) + : ElementBase (startSubPathElement), startPos (pos) { - colours.clear(); } -int ColourGradient::addColour (const double proportionAlongGradient, const Colour& colour) +const ValueTree RelativePointPath::StartSubPath::createTree() const { - // must be within the two end-points - jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); - - const double pos = jlimit (0.0, 1.0, proportionAlongGradient); - - int i; - for (i = 0; i < colours.size(); ++i) - if (colours.getReference(i).position > pos) - break; + ValueTree v (DrawablePath::ValueTreeWrapper::Element::startSubPathElement); + v.setProperty (DrawablePath::ValueTreeWrapper::point1, startPos.toString(), 0); + return v; +} - colours.insert (i, ColourPoint (pos, colour)); - return i; +void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +{ + path.startNewSubPath (startPos.resolve (coordFinder)); } -void ColourGradient::removeColour (int index) +RelativePoint* RelativePointPath::StartSubPath::getControlPoints (int& numPoints) { - jassert (index > 0 && index < colours.size() - 1); - colours.remove (index); + numPoints = 1; + return &startPos; } -void ColourGradient::multiplyOpacity (const float multiplier) throw() +RelativePointPath::CloseSubPath::CloseSubPath() + : ElementBase (closeSubPathElement) { - for (int i = 0; i < colours.size(); ++i) - { - Colour& c = colours.getReference(i).colour; - c = c.withMultipliedAlpha (multiplier); - } } -int ColourGradient::getNumColours() const throw() +const ValueTree RelativePointPath::CloseSubPath::createTree() const { - return colours.size(); + return ValueTree (DrawablePath::ValueTreeWrapper::Element::closeSubPathElement); } -double ColourGradient::getColourPosition (const int index) const throw() +void RelativePointPath::CloseSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder*) const { - if (((unsigned int) index) < (unsigned int) colours.size()) - return colours.getReference (index).position; + path.closeSubPath(); +} +RelativePoint* RelativePointPath::CloseSubPath::getControlPoints (int& numPoints) +{ + numPoints = 0; return 0; - } +} -const Colour ColourGradient::getColour (const int index) const throw() +RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_) + : ElementBase (lineToElement), endPoint (endPoint_) { - if (((unsigned int) index) < (unsigned int) colours.size()) - return colours.getReference (index).colour; - - return Colour(); } -void ColourGradient::setColour (int index, const Colour& newColour) throw() +const ValueTree RelativePointPath::LineTo::createTree() const { - if (((unsigned int) index) < (unsigned int) colours.size()) - colours.getReference (index).colour = newColour; + ValueTree v (DrawablePath::ValueTreeWrapper::Element::lineToElement); + v.setProperty (DrawablePath::ValueTreeWrapper::point1, endPoint.toString(), 0); + return v; } -const Colour ColourGradient::getColourAtPosition (const double position) const throw() +void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const { - jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 - - if (position <= 0 || colours.size() <= 1) - return colours.getReference(0).colour; - - int i = colours.size() - 1; - while (position < colours.getReference(i).position) - --i; - - const ColourPoint& p1 = colours.getReference (i); - - if (i >= colours.size() - 1) - return p1.colour; - - const ColourPoint& p2 = colours.getReference (i + 1); + path.lineTo (endPoint.resolve (coordFinder)); +} - return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position))); +RelativePoint* RelativePointPath::LineTo::getControlPoints (int& numPoints) +{ + numPoints = 1; + return &endPoint; } -int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock & lookupTable) const +RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, const RelativePoint& endPoint) + : ElementBase (quadraticToElement) { -#if JUCE_DEBUG - // trying to use the object without setting its co-ordinates? Have a careful read of - // the comments for the constructors. - jassert (point1.getX() != 987654.0f); -#endif + controlPoints[0] = controlPoint; + controlPoints[1] = endPoint; +} - const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), - 3 * (int) point1.transformedBy (transform) - .getDistanceFrom (point2.transformedBy (transform))); - lookupTable.malloc (numEntries); +const ValueTree RelativePointPath::QuadraticTo::createTree() const +{ + ValueTree v (DrawablePath::ValueTreeWrapper::Element::quadraticToElement); + v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); + v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); + return v; +} - if (colours.size() >= 2) - { - jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 +void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +{ + path.quadraticTo (controlPoints[0].resolve (coordFinder), + controlPoints[1].resolve (coordFinder)); +} - PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); - int index = 0; +RelativePoint* RelativePointPath::QuadraticTo::getControlPoints (int& numPoints) +{ + numPoints = 2; + return controlPoints; +} - for (int j = 1; j < colours.size(); ++j) - { - const ColourPoint& p = colours.getReference (j); - const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; - const PixelARGB pix2 (p.colour.getPixelARGB()); +RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const RelativePoint& controlPoint2, const RelativePoint& endPoint) + : ElementBase (cubicToElement) +{ + controlPoints[0] = controlPoint1; + controlPoints[1] = controlPoint2; + controlPoints[2] = endPoint; +} - for (int i = 0; i < numToDo; ++i) - { - jassert (index >= 0 && index < numEntries); +const ValueTree RelativePointPath::CubicTo::createTree() const +{ + ValueTree v (DrawablePath::ValueTreeWrapper::Element::cubicToElement); + v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); + v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); + v.setProperty (DrawablePath::ValueTreeWrapper::point3, controlPoints[2].toString(), 0); + return v; +} - lookupTable[index] = pix1; - lookupTable[index].tween (pix2, (i << 8) / numToDo); - ++index; - } +void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +{ + path.cubicTo (controlPoints[0].resolve (coordFinder), + controlPoints[1].resolve (coordFinder), + controlPoints[2].resolve (coordFinder)); +} - pix1 = pix2; - } +RelativePoint* RelativePointPath::CubicTo::getControlPoints (int& numPoints) +{ + numPoints = 3; + return controlPoints; +} - while (index < numEntries) - lookupTable [index++] = pix1; - } - else - { - jassertfalse; // no colours specified! - } +RelativeParallelogram::RelativeParallelogram() +{ +} - return numEntries; +RelativeParallelogram::RelativeParallelogram (const RelativePoint& topLeft_, const RelativePoint& topRight_, const RelativePoint& bottomLeft_) + : topLeft (topLeft_), topRight (topRight_), bottomLeft (bottomLeft_) +{ } -bool ColourGradient::isOpaque() const throw() +RelativeParallelogram::RelativeParallelogram (const String& topLeft_, const String& topRight_, const String& bottomLeft_) + : topLeft (topLeft_), topRight (topRight_), bottomLeft (bottomLeft_) { - for (int i = 0; i < colours.size(); ++i) - if (! colours.getReference(i).colour.isOpaque()) - return false; +} - return true; +RelativeParallelogram::~RelativeParallelogram() +{ } -bool ColourGradient::isInvisible() const throw() +void RelativeParallelogram::resolveThreePoints (Point* points, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const { - for (int i = 0; i < colours.size(); ++i) - if (! colours.getReference(i).colour.isTransparent()) - return false; + points[0] = topLeft.resolve (coordFinder); + points[1] = topRight.resolve (coordFinder); + points[2] = bottomLeft.resolve (coordFinder); +} - return true; +void RelativeParallelogram::resolveFourCorners (Point* points, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +{ + resolveThreePoints (points, coordFinder); + points[3] = points[1] + (points[2] - points[0]); } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_ColourGradient.cpp ***/ +const Rectangle RelativeParallelogram::getBounds (RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +{ + Point points[4]; + resolveFourCorners (points, coordFinder); + return Rectangle::findAreaContainingPoints (points, 4); +} +void RelativeParallelogram::getPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +{ + Point points[4]; + resolveFourCorners (points, coordFinder); -/*** Start of inlined file: juce_Colours.cpp ***/ -BEGIN_JUCE_NAMESPACE + path.startNewSubPath (points[0]); + path.lineTo (points[1]); + path.lineTo (points[3]); + path.lineTo (points[2]); + path.closeSubPath(); +} -const Colour Colours::transparentBlack (0); -const Colour Colours::transparentWhite (0x00ffffff); +const AffineTransform RelativeParallelogram::resetToPerpendicular (RelativeCoordinate::NamedCoordinateFinder* const coordFinder) +{ + Point corners[3]; + resolveThreePoints (corners, coordFinder); -const Colour Colours::aliceblue (0xfff0f8ff); -const Colour Colours::antiquewhite (0xfffaebd7); -const Colour Colours::aqua (0xff00ffff); -const Colour Colours::aquamarine (0xff7fffd4); -const Colour Colours::azure (0xfff0ffff); -const Colour Colours::beige (0xfff5f5dc); -const Colour Colours::bisque (0xffffe4c4); -const Colour Colours::black (0xff000000); -const Colour Colours::blanchedalmond (0xffffebcd); -const Colour Colours::blue (0xff0000ff); -const Colour Colours::blueviolet (0xff8a2be2); -const Colour Colours::brown (0xffa52a2a); -const Colour Colours::burlywood (0xffdeb887); -const Colour Colours::cadetblue (0xff5f9ea0); -const Colour Colours::chartreuse (0xff7fff00); -const Colour Colours::chocolate (0xffd2691e); -const Colour Colours::coral (0xffff7f50); -const Colour Colours::cornflowerblue (0xff6495ed); -const Colour Colours::cornsilk (0xfffff8dc); -const Colour Colours::crimson (0xffdc143c); -const Colour Colours::cyan (0xff00ffff); -const Colour Colours::darkblue (0xff00008b); -const Colour Colours::darkcyan (0xff008b8b); -const Colour Colours::darkgoldenrod (0xffb8860b); -const Colour Colours::darkgrey (0xff555555); -const Colour Colours::darkgreen (0xff006400); -const Colour Colours::darkkhaki (0xffbdb76b); -const Colour Colours::darkmagenta (0xff8b008b); -const Colour Colours::darkolivegreen (0xff556b2f); -const Colour Colours::darkorange (0xffff8c00); -const Colour Colours::darkorchid (0xff9932cc); -const Colour Colours::darkred (0xff8b0000); -const Colour Colours::darksalmon (0xffe9967a); -const Colour Colours::darkseagreen (0xff8fbc8f); -const Colour Colours::darkslateblue (0xff483d8b); -const Colour Colours::darkslategrey (0xff2f4f4f); -const Colour Colours::darkturquoise (0xff00ced1); -const Colour Colours::darkviolet (0xff9400d3); -const Colour Colours::deeppink (0xffff1493); -const Colour Colours::deepskyblue (0xff00bfff); -const Colour Colours::dimgrey (0xff696969); -const Colour Colours::dodgerblue (0xff1e90ff); -const Colour Colours::firebrick (0xffb22222); -const Colour Colours::floralwhite (0xfffffaf0); -const Colour Colours::forestgreen (0xff228b22); -const Colour Colours::fuchsia (0xffff00ff); -const Colour Colours::gainsboro (0xffdcdcdc); -const Colour Colours::gold (0xffffd700); -const Colour Colours::goldenrod (0xffdaa520); -const Colour Colours::grey (0xff808080); -const Colour Colours::green (0xff008000); -const Colour Colours::greenyellow (0xffadff2f); -const Colour Colours::honeydew (0xfff0fff0); -const Colour Colours::hotpink (0xffff69b4); -const Colour Colours::indianred (0xffcd5c5c); -const Colour Colours::indigo (0xff4b0082); -const Colour Colours::ivory (0xfffffff0); -const Colour Colours::khaki (0xfff0e68c); -const Colour Colours::lavender (0xffe6e6fa); -const Colour Colours::lavenderblush (0xfffff0f5); -const Colour Colours::lemonchiffon (0xfffffacd); -const Colour Colours::lightblue (0xffadd8e6); -const Colour Colours::lightcoral (0xfff08080); -const Colour Colours::lightcyan (0xffe0ffff); -const Colour Colours::lightgoldenrodyellow (0xfffafad2); -const Colour Colours::lightgreen (0xff90ee90); -const Colour Colours::lightgrey (0xffd3d3d3); -const Colour Colours::lightpink (0xffffb6c1); -const Colour Colours::lightsalmon (0xffffa07a); -const Colour Colours::lightseagreen (0xff20b2aa); -const Colour Colours::lightskyblue (0xff87cefa); -const Colour Colours::lightslategrey (0xff778899); -const Colour Colours::lightsteelblue (0xffb0c4de); -const Colour Colours::lightyellow (0xffffffe0); -const Colour Colours::lime (0xff00ff00); -const Colour Colours::limegreen (0xff32cd32); -const Colour Colours::linen (0xfffaf0e6); -const Colour Colours::magenta (0xffff00ff); -const Colour Colours::maroon (0xff800000); -const Colour Colours::mediumaquamarine (0xff66cdaa); -const Colour Colours::mediumblue (0xff0000cd); -const Colour Colours::mediumorchid (0xffba55d3); -const Colour Colours::mediumpurple (0xff9370db); -const Colour Colours::mediumseagreen (0xff3cb371); -const Colour Colours::mediumslateblue (0xff7b68ee); -const Colour Colours::mediumspringgreen (0xff00fa9a); -const Colour Colours::mediumturquoise (0xff48d1cc); -const Colour Colours::mediumvioletred (0xffc71585); -const Colour Colours::midnightblue (0xff191970); -const Colour Colours::mintcream (0xfff5fffa); -const Colour Colours::mistyrose (0xffffe4e1); -const Colour Colours::navajowhite (0xffffdead); -const Colour Colours::navy (0xff000080); -const Colour Colours::oldlace (0xfffdf5e6); -const Colour Colours::olive (0xff808000); -const Colour Colours::olivedrab (0xff6b8e23); -const Colour Colours::orange (0xffffa500); -const Colour Colours::orangered (0xffff4500); -const Colour Colours::orchid (0xffda70d6); -const Colour Colours::palegoldenrod (0xffeee8aa); -const Colour Colours::palegreen (0xff98fb98); -const Colour Colours::paleturquoise (0xffafeeee); -const Colour Colours::palevioletred (0xffdb7093); -const Colour Colours::papayawhip (0xffffefd5); -const Colour Colours::peachpuff (0xffffdab9); -const Colour Colours::peru (0xffcd853f); -const Colour Colours::pink (0xffffc0cb); -const Colour Colours::plum (0xffdda0dd); -const Colour Colours::powderblue (0xffb0e0e6); -const Colour Colours::purple (0xff800080); -const Colour Colours::red (0xffff0000); -const Colour Colours::rosybrown (0xffbc8f8f); -const Colour Colours::royalblue (0xff4169e1); -const Colour Colours::saddlebrown (0xff8b4513); -const Colour Colours::salmon (0xfffa8072); -const Colour Colours::sandybrown (0xfff4a460); -const Colour Colours::seagreen (0xff2e8b57); -const Colour Colours::seashell (0xfffff5ee); -const Colour Colours::sienna (0xffa0522d); -const Colour Colours::silver (0xffc0c0c0); -const Colour Colours::skyblue (0xff87ceeb); -const Colour Colours::slateblue (0xff6a5acd); -const Colour Colours::slategrey (0xff708090); -const Colour Colours::snow (0xfffffafa); -const Colour Colours::springgreen (0xff00ff7f); -const Colour Colours::steelblue (0xff4682b4); -const Colour Colours::tan (0xffd2b48c); -const Colour Colours::teal (0xff008080); -const Colour Colours::thistle (0xffd8bfd8); -const Colour Colours::tomato (0xffff6347); -const Colour Colours::turquoise (0xff40e0d0); -const Colour Colours::violet (0xffee82ee); -const Colour Colours::wheat (0xfff5deb3); -const Colour Colours::white (0xffffffff); -const Colour Colours::whitesmoke (0xfff5f5f5); -const Colour Colours::yellow (0xffffff00); -const Colour Colours::yellowgreen (0xff9acd32); + const Line top (corners[0], corners[1]); + const Line left (corners[0], corners[2]); + const Point newTopRight (corners[0] + Point (top.getLength(), 0.0f)); + const Point newBottomLeft (corners[0] + Point (0.0f, left.getLength())); -const Colour Colours::findColourForName (const String& colourName, - const Colour& defaultColour) + topRight.moveToAbsolute (newTopRight, coordFinder); + bottomLeft.moveToAbsolute (newBottomLeft, coordFinder); + + return AffineTransform::fromTargetPoints (corners[0].getX(), corners[0].getY(), corners[0].getX(), corners[0].getY(), + corners[1].getX(), corners[1].getY(), newTopRight.getX(), newTopRight.getY(), + corners[2].getX(), corners[2].getY(), newBottomLeft.getX(), newBottomLeft.getY()); +} + +bool RelativeParallelogram::operator== (const RelativeParallelogram& other) const throw() { - static const int presets[] = - { - // (first value is the string's hashcode, second is ARGB) + return topLeft == other.topLeft && topRight == other.topRight && bottomLeft == other.bottomLeft; +} - 0x05978fff, 0xff000000, /* black */ - 0x06bdcc29, 0xffffffff, /* white */ - 0x002e305a, 0xff0000ff, /* blue */ - 0x00308adf, 0xff808080, /* grey */ - 0x05e0cf03, 0xff008000, /* green */ - 0x0001b891, 0xffff0000, /* red */ - 0xd43c6474, 0xffffff00, /* yellow */ - 0x620886da, 0xfff0f8ff, /* aliceblue */ - 0x20a2676a, 0xfffaebd7, /* antiquewhite */ - 0x002dcebc, 0xff00ffff, /* aqua */ - 0x46bb5f7e, 0xff7fffd4, /* aquamarine */ - 0x0590228f, 0xfff0ffff, /* azure */ - 0x05947fe4, 0xfff5f5dc, /* beige */ - 0xad388e35, 0xffffe4c4, /* bisque */ - 0x00674f7e, 0xffffebcd, /* blanchedalmond */ - 0x39129959, 0xff8a2be2, /* blueviolet */ - 0x059a8136, 0xffa52a2a, /* brown */ - 0x89cea8f9, 0xffdeb887, /* burlywood */ - 0x0fa260cf, 0xff5f9ea0, /* cadetblue */ - 0x6b748956, 0xff7fff00, /* chartreuse */ - 0x2903623c, 0xffd2691e, /* chocolate */ - 0x05a74431, 0xffff7f50, /* coral */ - 0x618d42dd, 0xff6495ed, /* cornflowerblue */ - 0xe4b479fd, 0xfffff8dc, /* cornsilk */ - 0x3d8c4edf, 0xffdc143c, /* crimson */ - 0x002ed323, 0xff00ffff, /* cyan */ - 0x67cc74d0, 0xff00008b, /* darkblue */ - 0x67cd1799, 0xff008b8b, /* darkcyan */ - 0x31bbd168, 0xffb8860b, /* darkgoldenrod */ - 0x67cecf55, 0xff555555, /* darkgrey */ - 0x920b194d, 0xff006400, /* darkgreen */ - 0x923edd4c, 0xffbdb76b, /* darkkhaki */ - 0x5c293873, 0xff8b008b, /* darkmagenta */ - 0x6b6671fe, 0xff556b2f, /* darkolivegreen */ - 0xbcfd2524, 0xffff8c00, /* darkorange */ - 0xbcfdf799, 0xff9932cc, /* darkorchid */ - 0x55ee0d5b, 0xff8b0000, /* darkred */ - 0xc2e5f564, 0xffe9967a, /* darksalmon */ - 0x61be858a, 0xff8fbc8f, /* darkseagreen */ - 0xc2b0f2bd, 0xff483d8b, /* darkslateblue */ - 0xc2b34d42, 0xff2f4f4f, /* darkslategrey */ - 0x7cf2b06b, 0xff00ced1, /* darkturquoise */ - 0xc8769375, 0xff9400d3, /* darkviolet */ - 0x25832862, 0xffff1493, /* deeppink */ - 0xfcad568f, 0xff00bfff, /* deepskyblue */ - 0x634c8b67, 0xff696969, /* dimgrey */ - 0x45c1ce55, 0xff1e90ff, /* dodgerblue */ - 0xef19e3cb, 0xffb22222, /* firebrick */ - 0xb852b195, 0xfffffaf0, /* floralwhite */ - 0xd086fd06, 0xff228b22, /* forestgreen */ - 0xe106b6d7, 0xffff00ff, /* fuchsia */ - 0x7880d61e, 0xffdcdcdc, /* gainsboro */ - 0x00308060, 0xffffd700, /* gold */ - 0xb3b3bc1e, 0xffdaa520, /* goldenrod */ - 0xbab8a537, 0xffadff2f, /* greenyellow */ - 0xe4cacafb, 0xfff0fff0, /* honeydew */ - 0x41892743, 0xffff69b4, /* hotpink */ - 0xd5796f1a, 0xffcd5c5c, /* indianred */ - 0xb969fed2, 0xff4b0082, /* indigo */ - 0x05fef6a9, 0xfffffff0, /* ivory */ - 0x06149302, 0xfff0e68c, /* khaki */ - 0xad5a05c7, 0xffe6e6fa, /* lavender */ - 0x7c4d5b99, 0xfffff0f5, /* lavenderblush */ - 0x195756f0, 0xfffffacd, /* lemonchiffon */ - 0x28e4ea70, 0xffadd8e6, /* lightblue */ - 0xf3c7ccdb, 0xfff08080, /* lightcoral */ - 0x28e58d39, 0xffe0ffff, /* lightcyan */ - 0x21234e3c, 0xfffafad2, /* lightgoldenrodyellow */ - 0xf40157ad, 0xff90ee90, /* lightgreen */ - 0x28e744f5, 0xffd3d3d3, /* lightgrey */ - 0x28eb3b8c, 0xffffb6c1, /* lightpink */ - 0x9fb78304, 0xffffa07a, /* lightsalmon */ - 0x50632b2a, 0xff20b2aa, /* lightseagreen */ - 0x68fb7b25, 0xff87cefa, /* lightskyblue */ - 0xa8a35ba2, 0xff778899, /* lightslategrey */ - 0xa20d484f, 0xffb0c4de, /* lightsteelblue */ - 0xaa2cf10a, 0xffffffe0, /* lightyellow */ - 0x0032afd5, 0xff00ff00, /* lime */ - 0x607bbc4e, 0xff32cd32, /* limegreen */ - 0x06234efa, 0xfffaf0e6, /* linen */ - 0x316858a9, 0xffff00ff, /* magenta */ - 0xbf8ca470, 0xff800000, /* maroon */ - 0xbd58e0b3, 0xff66cdaa, /* mediumaquamarine */ - 0x967dfd4f, 0xff0000cd, /* mediumblue */ - 0x056f5c58, 0xffba55d3, /* mediumorchid */ - 0x07556b71, 0xff9370db, /* mediumpurple */ - 0x5369b689, 0xff3cb371, /* mediumseagreen */ - 0x066be19e, 0xff7b68ee, /* mediumslateblue */ - 0x3256b281, 0xff00fa9a, /* mediumspringgreen */ - 0xc0ad9f4c, 0xff48d1cc, /* mediumturquoise */ - 0x628e63dd, 0xffc71585, /* mediumvioletred */ - 0x168eb32a, 0xff191970, /* midnightblue */ - 0x4306b960, 0xfff5fffa, /* mintcream */ - 0x4cbc0e6b, 0xffffe4e1, /* mistyrose */ - 0xe97218a6, 0xffffdead, /* navajowhite */ - 0x00337bb6, 0xff000080, /* navy */ - 0xadd2d33e, 0xfffdf5e6, /* oldlace */ - 0x064ee1db, 0xff808000, /* olive */ - 0x9e33a98a, 0xff6b8e23, /* olivedrab */ - 0xc3de262e, 0xffffa500, /* orange */ - 0x58bebba3, 0xffff4500, /* orangered */ - 0xc3def8a3, 0xffda70d6, /* orchid */ - 0x28cb4834, 0xffeee8aa, /* palegoldenrod */ - 0x3d9dd619, 0xff98fb98, /* palegreen */ - 0x74022737, 0xffafeeee, /* paleturquoise */ - 0x15e2ebc8, 0xffdb7093, /* palevioletred */ - 0x5fd898e2, 0xffffefd5, /* papayawhip */ - 0x93e1b776, 0xffffdab9, /* peachpuff */ - 0x003472f8, 0xffcd853f, /* peru */ - 0x00348176, 0xffffc0cb, /* pink */ - 0x00348d94, 0xffdda0dd, /* plum */ - 0xd036be93, 0xffb0e0e6, /* powderblue */ - 0xc5c507bc, 0xff800080, /* purple */ - 0xa89d65b3, 0xffbc8f8f, /* rosybrown */ - 0xbd9413e1, 0xff4169e1, /* royalblue */ - 0xf456044f, 0xff8b4513, /* saddlebrown */ - 0xc9c6f66e, 0xfffa8072, /* salmon */ - 0x0bb131e1, 0xfff4a460, /* sandybrown */ - 0x34636c14, 0xff2e8b57, /* seagreen */ - 0x3507fb41, 0xfffff5ee, /* seashell */ - 0xca348772, 0xffa0522d, /* sienna */ - 0xca37d30d, 0xffc0c0c0, /* silver */ - 0x80da74fb, 0xff87ceeb, /* skyblue */ - 0x44a8dd73, 0xff6a5acd, /* slateblue */ - 0x44ab37f8, 0xff708090, /* slategrey */ - 0x0035f183, 0xfffffafa, /* snow */ - 0xd5440d16, 0xff00ff7f, /* springgreen */ - 0x3e1524a5, 0xff4682b4, /* steelblue */ - 0x0001bfa1, 0xffd2b48c, /* tan */ - 0x0036425c, 0xff008080, /* teal */ - 0xafc8858f, 0xffd8bfd8, /* thistle */ - 0xcc41600a, 0xffff6347, /* tomato */ - 0xfeea9b21, 0xff40e0d0, /* turquoise */ - 0xcf57947f, 0xffee82ee, /* violet */ - 0x06bdbae7, 0xfff5deb3, /* wheat */ - 0x10802ee6, 0xfff5f5f5, /* whitesmoke */ - 0xe1b5130f, 0xff9acd32 /* yellowgreen */ - }; +bool RelativeParallelogram::operator!= (const RelativeParallelogram& other) const throw() +{ + return ! operator== (other); +} - const int hash = colourName.trim().toLowerCase().hashCode(); +const Point RelativeParallelogram::getInternalCoordForPoint (const Point* const corners, Point target) throw() +{ + const Point tr (corners[1] - corners[0]); + const Point bl (corners[2] - corners[0]); + target -= corners[0]; - for (int i = 0; i < numElementsInArray (presets); i += 2) - if (presets [i] == hash) - return Colour (presets [i + 1]); + return Point (Line (Point(), tr).getIntersection (Line (target, target - bl)).getDistanceFromOrigin(), + Line (Point(), bl).getIntersection (Line (target, target - tr)).getDistanceFromOrigin()); +} - return defaultColour; +const Point RelativeParallelogram::getPointForInternalCoord (const Point* const corners, const Point& point) throw() +{ + return corners[0] + + Line (Point(), corners[1] - corners[0]).getPointAlongLine (point.getX()) + + Line (Point(), corners[2] - corners[0]).getPointAlongLine (point.getY()); } END_JUCE_NAMESPACE -/*** End of inlined file: juce_Colours.cpp ***/ +/*** End of inlined file: juce_RelativeCoordinate.cpp ***/ +#endif -/*** Start of inlined file: juce_EdgeTable.cpp ***/ -BEGIN_JUCE_NAMESPACE +#if JUCE_BUILD_MISC // (put these in misc to balance the file sizes and avoid problems in iphone build) -const int juce_edgeTableDefaultEdgesPerLine = 32; +/*** Start of inlined file: juce_Colour.cpp ***/ +BEGIN_JUCE_NAMESPACE -EdgeTable::EdgeTable (const Rectangle& bounds_, - const Path& path, const AffineTransform& transform) - : bounds (bounds_), - maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), - lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), - needToCheckEmptinesss (true) +namespace ColourHelpers { - table.malloc ((bounds.getHeight() + 1) * lineStrideElements); - int* t = table; - - for (int i = bounds.getHeight(); --i >= 0;) + static uint8 floatAlphaToInt (const float alpha) throw() { - *t = 0; - t += lineStrideElements; + return (uint8) jlimit (0, 0xff, roundToInt (alpha * 255.0f)); } - const int topLimit = bounds.getY() << 8; - const int heightLimit = bounds.getHeight() << 8; - const int leftLimit = bounds.getX() << 8; - const int rightLimit = bounds.getRight() << 8; - - PathFlatteningIterator iter (path, transform); - - while (iter.next()) + static void convertHSBtoRGB (float h, float s, float v, + uint8& r, uint8& g, uint8& b) throw() { - int y1 = roundToInt (iter.y1 * 256.0f); - int y2 = roundToInt (iter.y2 * 256.0f); + v = jlimit (0.0f, 1.0f, v); + v *= 255.0f; + const uint8 intV = (uint8) roundToInt (v); - if (y1 != y2) + if (s <= 0) { - y1 -= topLimit; - y2 -= topLimit; + r = intV; + g = intV; + b = intV; + } + else + { + s = jmin (1.0f, s); + h = jlimit (0.0f, 1.0f, h); + h = (h - std::floor (h)) * 6.0f + 0.00001f; // need a small adjustment to compensate for rounding errors + const float f = h - std::floor (h); - const int startY = y1; - int direction = -1; + const uint8 x = (uint8) roundToInt (v * (1.0f - s)); + const float y = v * (1.0f - s * f); + const float z = v * (1.0f - (s * (1.0f - f))); - if (y1 > y2) + if (h < 1.0f) { - swapVariables (y1, y2); - direction = 1; + r = intV; + g = (uint8) roundToInt (z); + b = x; } - - if (y1 < 0) - y1 = 0; - - if (y2 > heightLimit) - y2 = heightLimit; - - if (y1 < y2) + else if (h < 2.0f) { - const double startX = 256.0f * iter.x1; - const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); - const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); - - do - { - const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); - int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); - - if (x < leftLimit) - x = leftLimit; - else if (x >= rightLimit) - x = rightLimit - 1; - - addEdgePoint (x, y1 >> 8, direction * step); - y1 += step; - } - while (y1 < y2); + r = (uint8) roundToInt (y); + g = intV; + b = x; + } + else if (h < 3.0f) + { + r = x; + g = intV; + b = (uint8) roundToInt (z); + } + else if (h < 4.0f) + { + r = x; + g = (uint8) roundToInt (y); + b = intV; + } + else if (h < 5.0f) + { + r = (uint8) roundToInt (z); + g = x; + b = intV; + } + else if (h < 6.0f) + { + r = intV; + g = x; + b = (uint8) roundToInt (y); + } + else + { + r = 0; + g = 0; + b = 0; } } } - - sanitiseLevels (path.isUsingNonZeroWinding()); } -EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) - : bounds (rectangleToAdd), - maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), - lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), - needToCheckEmptinesss (true) +Colour::Colour() throw() + : argb (0) { - table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); - table[0] = 0; - - const int x1 = rectangleToAdd.getX() << 8; - const int x2 = rectangleToAdd.getRight() << 8; - - int* t = table; - for (int i = rectangleToAdd.getHeight(); --i >= 0;) - { - t[0] = 2; - t[1] = x1; - t[2] = 255; - t[3] = x2; - t[4] = 0; - t += lineStrideElements; - } } -EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) - : bounds (rectanglesToAdd.getBounds()), - maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), - lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), - needToCheckEmptinesss (true) +Colour::Colour (const Colour& other) throw() + : argb (other.argb) { - table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); - - int* t = table; - for (int i = bounds.getHeight(); --i >= 0;) - { - *t = 0; - t += lineStrideElements; - } - - for (RectangleList::Iterator iter (rectanglesToAdd); iter.next();) - { - const Rectangle* const r = iter.getRectangle(); - - const int x1 = r->getX() << 8; - const int x2 = r->getRight() << 8; - int y = r->getY() - bounds.getY(); - - for (int j = r->getHeight(); --j >= 0;) - { - addEdgePoint (x1, y, 255); - addEdgePoint (x2, y, -255); - ++y; - } - } - - sanitiseLevels (true); } -EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) - : bounds (Rectangle ((int) std::floor (rectangleToAdd.getX()), - roundToInt (rectangleToAdd.getY() * 256.0f) >> 8, - 2 + (int) rectangleToAdd.getWidth(), - 2 + (int) rectangleToAdd.getHeight())), - maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), - lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), - needToCheckEmptinesss (true) +Colour& Colour::operator= (const Colour& other) throw() { - jassert (! rectangleToAdd.isEmpty()); - table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); - table[0] = 0; - - const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f); - const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f); - - int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8); - jassert (y1 < 256); - int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8); - - if (x2 <= x1 || y2 <= y1) - { - bounds.setHeight (0); - return; - } - - int lineY = 0; - int* t = table; - - if ((y1 >> 8) == (y2 >> 8)) - { - t[0] = 2; - t[1] = x1; - t[2] = y2 - y1; - t[3] = x2; - t[4] = 0; - ++lineY; - t += lineStrideElements; - } - else - { - t[0] = 2; - t[1] = x1; - t[2] = 255 - (y1 & 255); - t[3] = x2; - t[4] = 0; - ++lineY; - t += lineStrideElements; - - while (lineY < (y2 >> 8)) - { - t[0] = 2; - t[1] = x1; - t[2] = 255; - t[3] = x2; - t[4] = 0; - ++lineY; - t += lineStrideElements; - } - - jassert (lineY < bounds.getHeight()); - t[0] = 2; - t[1] = x1; - t[2] = y2 & 255; - t[3] = x2; - t[4] = 0; - ++lineY; - t += lineStrideElements; - } - - while (lineY < bounds.getHeight()) - { - t[0] = 0; - t += lineStrideElements; - ++lineY; - } + argb = other.argb; + return *this; } -EdgeTable::EdgeTable (const EdgeTable& other) +bool Colour::operator== (const Colour& other) const throw() { - operator= (other); + return argb.getARGB() == other.argb.getARGB(); } -EdgeTable& EdgeTable::operator= (const EdgeTable& other) +bool Colour::operator!= (const Colour& other) const throw() { - bounds = other.bounds; - maxEdgesPerLine = other.maxEdgesPerLine; - lineStrideElements = other.lineStrideElements; - needToCheckEmptinesss = other.needToCheckEmptinesss; - - table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); - copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); - return *this; + return argb.getARGB() != other.argb.getARGB(); } -EdgeTable::~EdgeTable() +Colour::Colour (const uint32 argb_) throw() + : argb (argb_) { } -void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) throw() +Colour::Colour (const uint8 red, + const uint8 green, + const uint8 blue) throw() { - while (--numLines >= 0) - { - memcpy (dest, src, (src[0] * 2 + 1) * sizeof (int)); - src += srcLineStride; - dest += destLineStride; - } + argb.setARGB (0xff, red, green, blue); } -void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) throw() +const Colour Colour::fromRGB (const uint8 red, + const uint8 green, + const uint8 blue) throw() { - // Convert the table from relative windings to absolute levels.. - int* lineStart = table; + return Colour (red, green, blue); +} - for (int i = bounds.getHeight(); --i >= 0;) - { - int* line = lineStart; - lineStart += lineStrideElements; +Colour::Colour (const uint8 red, + const uint8 green, + const uint8 blue, + const uint8 alpha) throw() +{ + argb.setARGB (alpha, red, green, blue); +} - int num = *line; - if (num == 0) - continue; +const Colour Colour::fromRGBA (const uint8 red, + const uint8 green, + const uint8 blue, + const uint8 alpha) throw() +{ + return Colour (red, green, blue, alpha); +} - int level = 0; +Colour::Colour (const uint8 red, + const uint8 green, + const uint8 blue, + const float alpha) throw() +{ + argb.setARGB (ColourHelpers::floatAlphaToInt (alpha), red, green, blue); +} - if (useNonZeroWinding) - { - while (--num > 0) - { - line += 2; - level += *line; - int corrected = abs (level); - if (corrected >> 8) - corrected = 255; +const Colour Colour::fromRGBAFloat (const uint8 red, + const uint8 green, + const uint8 blue, + const float alpha) throw() +{ + return Colour (red, green, blue, alpha); +} - *line = corrected; - } - } - else - { - while (--num > 0) - { - line += 2; - level += *line; - int corrected = abs (level); - if (corrected >> 8) - { - corrected &= 511; - if (corrected >> 8) - corrected = 511 - corrected; - } +Colour::Colour (const float hue, + const float saturation, + const float brightness, + const float alpha) throw() +{ + uint8 r = getRed(), g = getGreen(), b = getBlue(); + ColourHelpers::convertHSBtoRGB (hue, saturation, brightness, r, g, b); - *line = corrected; - } - } + argb.setARGB (ColourHelpers::floatAlphaToInt (alpha), r, g, b); +} - line[2] = 0; // force the last level to 0, just in case something went wrong in creating the table - } +const Colour Colour::fromHSV (const float hue, + const float saturation, + const float brightness, + const float alpha) throw() +{ + return Colour (hue, saturation, brightness, alpha); } -void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) throw() +Colour::Colour (const float hue, + const float saturation, + const float brightness, + const uint8 alpha) throw() { - if (newNumEdgesPerLine != maxEdgesPerLine) - { - maxEdgesPerLine = newNumEdgesPerLine; + uint8 r = getRed(), g = getGreen(), b = getBlue(); + ColourHelpers::convertHSBtoRGB (hue, saturation, brightness, r, g, b); - jassert (bounds.getHeight() > 0); - const int newLineStrideElements = maxEdgesPerLine * 2 + 1; + argb.setARGB (alpha, r, g, b); +} - HeapBlock newTable (bounds.getHeight() * newLineStrideElements); +Colour::~Colour() throw() +{ +} - copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); +const PixelARGB Colour::getPixelARGB() const throw() +{ + PixelARGB p (argb); + p.premultiply(); + return p; +} - table.swapWith (newTable); - lineStrideElements = newLineStrideElements; - } +uint32 Colour::getARGB() const throw() +{ + return argb.getARGB(); } -void EdgeTable::optimiseTable() throw() +bool Colour::isTransparent() const throw() { - int maxLineElements = 0; + return getAlpha() == 0; +} - for (int i = bounds.getHeight(); --i >= 0;) - maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]); +bool Colour::isOpaque() const throw() +{ + return getAlpha() == 0xff; +} - remapTableForNumEdges (maxLineElements); +const Colour Colour::withAlpha (const uint8 newAlpha) const throw() +{ + PixelARGB newCol (argb); + newCol.setAlpha (newAlpha); + return Colour (newCol.getARGB()); } -void EdgeTable::addEdgePoint (const int x, const int y, const int winding) throw() +const Colour Colour::withAlpha (const float newAlpha) const throw() { - jassert (y >= 0 && y < bounds.getHeight()); + jassert (newAlpha >= 0 && newAlpha <= 1.0f); - int* line = table + lineStrideElements * y; - const int numPoints = line[0]; - int n = numPoints << 1; + PixelARGB newCol (argb); + newCol.setAlpha (ColourHelpers::floatAlphaToInt (newAlpha)); + return Colour (newCol.getARGB()); +} - if (n > 0) - { - while (n > 0) - { - const int cx = line [n - 1]; +const Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const throw() +{ + jassert (alphaMultiplier >= 0); - if (cx <= x) - { - if (cx == x) - { - line [n] += winding; - return; - } + PixelARGB newCol (argb); + newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); + return Colour (newCol.getARGB()); +} - break; - } +const Colour Colour::overlaidWith (const Colour& src) const throw() +{ + const int destAlpha = getAlpha(); - n -= 2; - } + if (destAlpha > 0) + { + const int invA = 0xff - (int) src.getAlpha(); + const int resA = 0xff - (((0xff - destAlpha) * invA) >> 8); - if (numPoints >= maxEdgesPerLine) + if (resA > 0) { - remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); - jassert (numPoints < maxEdgesPerLine); - line = table + lineStrideElements * y; + const int da = (invA * destAlpha) / resA; + + return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), + (uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), + (uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), + (uint8) resA); } - memmove (line + (n + 3), line + (n + 1), sizeof (int) * ((numPoints << 1) - n)); + return *this; + } + else + { + return src; } - - line [n + 1] = x; - line [n + 2] = winding; - line[0]++; } -void EdgeTable::translate (float dx, const int dy) throw() +const Colour Colour::interpolatedWith (const Colour& other, float proportionOfOther) const throw() { - bounds.translate ((int) std::floor (dx), dy); + if (proportionOfOther <= 0) + return *this; - int* lineStart = table; - const int intDx = (int) (dx * 256.0f); + if (proportionOfOther >= 1.0f) + return other; - for (int i = bounds.getHeight(); --i >= 0;) - { - int* line = lineStart; - lineStart += lineStrideElements; - int num = *line++; + PixelARGB c1 (getPixelARGB()); + const PixelARGB c2 (other.getPixelARGB()); + c1.tween (c2, roundToInt (proportionOfOther * 255.0f)); + c1.unpremultiply(); - while (--num >= 0) - { - *line += intDx; - line += 2; - } - } + return Colour (c1.getARGB()); } -void EdgeTable::intersectWithEdgeTableLine (const int y, const int* otherLine) throw() +float Colour::getFloatRed() const throw() { - jassert (y >= 0 && y < bounds.getHeight()); - - int* dest = table + lineStrideElements * y; - if (dest[0] == 0) - return; - - int otherNumPoints = *otherLine; - if (otherNumPoints == 0) - { - *dest = 0; - return; - } - - const int right = bounds.getRight() << 8; + return getRed() / 255.0f; +} - // optimise for the common case where our line lies entirely within a - // single pair of points, as happens when clipping to a simple rect. - if (otherNumPoints == 2 && otherLine[2] >= 255) - { - clipEdgeTableLineToRange (dest, otherLine[1], jmin (right, otherLine[3])); - return; - } +float Colour::getFloatGreen() const throw() +{ + return getGreen() / 255.0f; +} - ++otherLine; - const size_t lineSizeBytes = (dest[0] * 2 + 1) * sizeof (int); - int* temp = (int*) alloca (lineSizeBytes); - memcpy (temp, dest, lineSizeBytes); +float Colour::getFloatBlue() const throw() +{ + return getBlue() / 255.0f; +} - const int* src1 = temp; - int srcNum1 = *src1++; - int x1 = *src1++; +float Colour::getFloatAlpha() const throw() +{ + return getAlpha() / 255.0f; +} - const int* src2 = otherLine; - int srcNum2 = otherNumPoints; - int x2 = *src2++; +void Colour::getHSB (float& h, float& s, float& v) const throw() +{ + const int r = getRed(); + const int g = getGreen(); + const int b = getBlue(); - int destIndex = 0, destTotal = 0; - int level1 = 0, level2 = 0; - int lastX = std::numeric_limits::min(), lastLevel = 0; + const int hi = jmax (r, g, b); + const int lo = jmin (r, g, b); - while (srcNum1 > 0 && srcNum2 > 0) + if (hi != 0) { - int nextX; - - if (x1 < x2) - { - nextX = x1; - level1 = *src1++; - x1 = *src1++; - --srcNum1; - } - else if (x1 == x2) - { - nextX = x1; - level1 = *src1++; - level2 = *src2++; - x1 = *src1++; - x2 = *src2++; - --srcNum1; - --srcNum2; - } - else - { - nextX = x2; - level2 = *src2++; - x2 = *src2++; - --srcNum2; - } + s = (hi - lo) / (float) hi; - if (nextX > lastX) + if (s != 0) { - if (nextX >= right) - break; + const float invDiff = 1.0f / (hi - lo); - lastX = nextX; + const float red = (hi - r) * invDiff; + const float green = (hi - g) * invDiff; + const float blue = (hi - b) * invDiff; - const int nextLevel = (level1 * (level2 + 1)) >> 8; - jassert (((unsigned int) nextLevel) < (unsigned int) 256); + if (r == hi) + h = blue - green; + else if (g == hi) + h = 2.0f + red - blue; + else + h = 4.0f + green - red; - if (nextLevel != lastLevel) - { - if (destTotal >= maxEdgesPerLine) - { - dest[0] = destTotal; - remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); - dest = table + lineStrideElements * y; - } + h *= 1.0f / 6.0f; - ++destTotal; - lastLevel = nextLevel; - dest[++destIndex] = nextX; - dest[++destIndex] = nextLevel; - } + if (h < 0) + ++h; } - } - - if (lastLevel > 0) - { - if (destTotal >= maxEdgesPerLine) + else { - dest[0] = destTotal; - remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); - dest = table + lineStrideElements * y; + h = 0; } - - ++destTotal; - dest[++destIndex] = right; - dest[++destIndex] = 0; } - - dest[0] = destTotal; - -#if JUCE_DEBUG - int last = std::numeric_limits::min(); - for (int i = 0; i < dest[0]; ++i) + else { - jassert (dest[i * 2 + 1] > last); - last = dest[i * 2 + 1]; + s = 0; + h = 0; } - jassert (dest [dest[0] * 2] == 0); -#endif + v = hi / 255.0f; } -void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) throw() +float Colour::getHue() const throw() { - int* lastItem = dest + (dest[0] * 2 - 1); - - if (x2 < lastItem[0]) - { - if (x2 <= dest[1]) - { - dest[0] = 0; - return; - } - - while (x2 < lastItem[-2]) - { - --(dest[0]); - lastItem -= 2; - } - - lastItem[0] = x2; - lastItem[1] = 0; - } - - if (x1 > dest[1]) - { - while (lastItem[0] > x1) - lastItem -= 2; - - const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2; - - if (itemsRemoved > 0) - { - dest[0] -= itemsRemoved; - memmove (dest + 1, lastItem, dest[0] * (sizeof (int) * 2)); - } - - dest[1] = x1; - } + float h, s, b; + getHSB (h, s, b); + return h; } -void EdgeTable::clipToRectangle (const Rectangle& r) throw() +const Colour Colour::withHue (const float hue) const throw() { - const Rectangle clipped (r.getIntersection (bounds)); - - if (clipped.isEmpty()) - { - needToCheckEmptinesss = false; - bounds.setHeight (0); - } - else - { - const int top = clipped.getY() - bounds.getY(); - const int bottom = clipped.getBottom() - bounds.getY(); + float h, s, b; + getHSB (h, s, b); - if (bottom < bounds.getHeight()) - bounds.setHeight (bottom); + return Colour (hue, s, b, getAlpha()); +} - if (clipped.getRight() < bounds.getRight()) - bounds.setRight (clipped.getRight()); +const Colour Colour::withRotatedHue (const float amountToRotate) const throw() +{ + float h, s, b; + getHSB (h, s, b); - for (int i = top; --i >= 0;) - table [lineStrideElements * i] = 0; + h += amountToRotate; + h -= std::floor (h); - if (clipped.getX() > bounds.getX()) - { - const int x1 = clipped.getX() << 8; - const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8; - int* line = table + lineStrideElements * top; + return Colour (h, s, b, getAlpha()); +} - for (int i = bottom - top; --i >= 0;) - { - if (line[0] != 0) - clipEdgeTableLineToRange (line, x1, x2); +float Colour::getSaturation() const throw() +{ + float h, s, b; + getHSB (h, s, b); + return s; +} - line += lineStrideElements; - } - } +const Colour Colour::withSaturation (const float saturation) const throw() +{ + float h, s, b; + getHSB (h, s, b); - needToCheckEmptinesss = true; - } + return Colour (h, saturation, b, getAlpha()); } -void EdgeTable::excludeRectangle (const Rectangle& r) throw() +const Colour Colour::withMultipliedSaturation (const float amount) const throw() { - const Rectangle clipped (r.getIntersection (bounds)); - - if (! clipped.isEmpty()) - { - const int top = clipped.getY() - bounds.getY(); - const int bottom = clipped.getBottom() - bounds.getY(); + float h, s, b; + getHSB (h, s, b); - //XXX optimise here by shortening the table if it fills top or bottom + return Colour (h, jmin (1.0f, s * amount), b, getAlpha()); +} - const int rectLine[] = { 4, std::numeric_limits::min(), 255, - clipped.getX() << 8, 0, - clipped.getRight() << 8, 255, - std::numeric_limits::max(), 0 }; +float Colour::getBrightness() const throw() +{ + float h, s, b; + getHSB (h, s, b); + return b; +} - for (int i = top; i < bottom; ++i) - intersectWithEdgeTableLine (i, rectLine); +const Colour Colour::withBrightness (const float brightness) const throw() +{ + float h, s, b; + getHSB (h, s, b); - needToCheckEmptinesss = true; - } + return Colour (h, s, brightness, getAlpha()); } -void EdgeTable::clipToEdgeTable (const EdgeTable& other) +const Colour Colour::withMultipliedBrightness (const float amount) const throw() { - const Rectangle clipped (other.bounds.getIntersection (bounds)); + float h, s, b; + getHSB (h, s, b); - if (clipped.isEmpty()) - { - needToCheckEmptinesss = false; - bounds.setHeight (0); - } - else - { - const int top = clipped.getY() - bounds.getY(); - const int bottom = clipped.getBottom() - bounds.getY(); + b *= amount; - if (bottom < bounds.getHeight()) - bounds.setHeight (bottom); + if (b > 1.0f) + b = 1.0f; - if (clipped.getRight() < bounds.getRight()) - bounds.setRight (clipped.getRight()); + return Colour (h, s, b, getAlpha()); +} - int i = 0; - for (i = top; --i >= 0;) - table [lineStrideElements * i] = 0; +const Colour Colour::brighter (float amount) const throw() +{ + amount = 1.0f / (1.0f + amount); - const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); + return Colour ((uint8) (255 - (amount * (255 - getRed()))), + (uint8) (255 - (amount * (255 - getGreen()))), + (uint8) (255 - (amount * (255 - getBlue()))), + getAlpha()); +} - for (i = top; i < bottom; ++i) - { - intersectWithEdgeTableLine (i, otherLine); - otherLine += other.lineStrideElements; - } +const Colour Colour::darker (float amount) const throw() +{ + amount = 1.0f / (1.0f + amount); - needToCheckEmptinesss = true; - } + return Colour ((uint8) (amount * getRed()), + (uint8) (amount * getGreen()), + (uint8) (amount * getBlue()), + getAlpha()); } -void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) throw() +const Colour Colour::greyLevel (const float brightness) throw() { - y -= bounds.getY(); - - if (y < 0 || y >= bounds.getHeight()) - return; + const uint8 level + = (uint8) jlimit (0x00, 0xff, roundToInt (brightness * 255.0f)); - needToCheckEmptinesss = true; + return Colour (level, level, level); +} - if (numPixels <= 0) - { - table [lineStrideElements * y] = 0; - return; - } +const Colour Colour::contrasting (const float amount) const throw() +{ + return overlaidWith ((((int) getRed() + (int) getGreen() + (int) getBlue() >= 3 * 128) + ? Colours::black + : Colours::white).withAlpha (amount)); +} - int* tempLine = (int*) alloca ((numPixels * 2 + 4) * sizeof (int)); - int destIndex = 0, lastLevel = 0; +const Colour Colour::contrasting (const Colour& colour1, + const Colour& colour2) throw() +{ + const float b1 = colour1.getBrightness(); + const float b2 = colour2.getBrightness(); + float best = 0.0f; + float bestDist = 0.0f; - while (--numPixels >= 0) + for (float i = 0.0f; i < 1.0f; i += 0.02f) { - const int alpha = *mask; - mask += maskStride; + const float d1 = std::abs (i - b1); + const float d2 = std::abs (i - b2); + const float dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); - if (alpha != lastLevel) + if (dist > bestDist) { - tempLine[++destIndex] = (x << 8); - tempLine[++destIndex] = alpha; - lastLevel = alpha; + best = i; + bestDist = dist; } - - ++x; - } - - if (lastLevel > 0) - { - tempLine[++destIndex] = (x << 8); - tempLine[++destIndex] = 0; } - tempLine[0] = destIndex >> 1; - - intersectWithEdgeTableLine (y, tempLine); + return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) + .withBrightness (best); } -bool EdgeTable::isEmpty() throw() +const String Colour::toString() const { - if (needToCheckEmptinesss) - { - needToCheckEmptinesss = false; - int* t = table; - - for (int i = bounds.getHeight(); --i >= 0;) - { - if (t[0] > 1) - return false; - - t += lineStrideElements; - } + return String::toHexString ((int) argb.getARGB()); +} - bounds.setHeight (0); - } +const Colour Colour::fromString (const String& encodedColourString) +{ + return Colour ((uint32) encodedColourString.getHexValue32()); +} - return bounds.getHeight() == 0; +const String Colour::toDisplayString (const bool includeAlphaValue) const +{ + return String::toHexString ((int) (argb.getARGB() & (includeAlphaValue ? 0xffffffff : 0xffffff))) + .paddedLeft ('0', includeAlphaValue ? 8 : 6) + .toUpperCase(); } END_JUCE_NAMESPACE -/*** End of inlined file: juce_EdgeTable.cpp ***/ +/*** End of inlined file: juce_Colour.cpp ***/ -/*** Start of inlined file: juce_FillType.cpp ***/ + +/*** Start of inlined file: juce_ColourGradient.cpp ***/ BEGIN_JUCE_NAMESPACE -FillType::FillType() throw() - : colour (0xff000000), image (0) +ColourGradient::ColourGradient() throw() { +#if JUCE_DEBUG + point1.setX (987654.0f); +#endif } -FillType::FillType (const Colour& colour_) throw() - : colour (colour_), image (0) +ColourGradient::ColourGradient (const Colour& colour1, const float x1_, const float y1_, + const Colour& colour2, const float x2_, const float y2_, + const bool isRadial_) + : point1 (x1_, y1_), + point2 (x2_, y2_), + isRadial (isRadial_) { + colours.add (ColourPoint (0.0, colour1)); + colours.add (ColourPoint (1.0, colour2)); } -FillType::FillType (const ColourGradient& gradient_) - : colour (0xff000000), gradient (new ColourGradient (gradient_)), image (0) +ColourGradient::~ColourGradient() { } -FillType::FillType (const Image& image_, const AffineTransform& transform_) throw() - : colour (0xff000000), image (image_), transform (transform_) +bool ColourGradient::operator== (const ColourGradient& other) const throw() { + return point1 == other.point1 && point2 == other.point2 + && isRadial == other.isRadial + && colours == other.colours; } -FillType::FillType (const FillType& other) - : colour (other.colour), - gradient (other.gradient != 0 ? new ColourGradient (*other.gradient) : 0), - image (other.image), transform (other.transform) +bool ColourGradient::operator!= (const ColourGradient& other) const throw() +{ + return ! operator== (other); +} + +void ColourGradient::clearColours() { + colours.clear(); } -FillType& FillType::operator= (const FillType& other) +int ColourGradient::addColour (const double proportionAlongGradient, const Colour& colour) { - if (this != &other) + // must be within the two end-points + jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); + + const double pos = jlimit (0.0, 1.0, proportionAlongGradient); + + int i; + for (i = 0; i < colours.size(); ++i) + if (colours.getReference(i).position > pos) + break; + + colours.insert (i, ColourPoint (pos, colour)); + return i; +} + +void ColourGradient::removeColour (int index) +{ + jassert (index > 0 && index < colours.size() - 1); + colours.remove (index); +} + +void ColourGradient::multiplyOpacity (const float multiplier) throw() +{ + for (int i = 0; i < colours.size(); ++i) { - colour = other.colour; - gradient = (other.gradient != 0 ? new ColourGradient (*other.gradient) : 0); - image = other.image; - transform = other.transform; + Colour& c = colours.getReference(i).colour; + c = c.withMultipliedAlpha (multiplier); } - - return *this; } -FillType::~FillType() throw() +int ColourGradient::getNumColours() const throw() { + return colours.size(); } -bool FillType::operator== (const FillType& other) const +double ColourGradient::getColourPosition (const int index) const throw() { - return colour == other.colour && image == other.image - && transform == other.transform - && (gradient == other.gradient - || (gradient != 0 && other.gradient != 0 && *gradient == *other.gradient)); + if (((unsigned int) index) < (unsigned int) colours.size()) + return colours.getReference (index).position; + + return 0; + } + +const Colour ColourGradient::getColour (const int index) const throw() +{ + if (((unsigned int) index) < (unsigned int) colours.size()) + return colours.getReference (index).colour; + + return Colour(); } -bool FillType::operator!= (const FillType& other) const +void ColourGradient::setColour (int index, const Colour& newColour) throw() { - return ! operator== (other); + if (((unsigned int) index) < (unsigned int) colours.size()) + colours.getReference (index).colour = newColour; } -void FillType::setColour (const Colour& newColour) throw() +const Colour ColourGradient::getColourAtPosition (const double position) const throw() { - gradient = 0; - image = Image(); - colour = newColour; + jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 + + if (position <= 0 || colours.size() <= 1) + return colours.getReference(0).colour; + + int i = colours.size() - 1; + while (position < colours.getReference(i).position) + --i; + + const ColourPoint& p1 = colours.getReference (i); + + if (i >= colours.size() - 1) + return p1.colour; + + const ColourPoint& p2 = colours.getReference (i + 1); + + return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position))); } -void FillType::setGradient (const ColourGradient& newGradient) +int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock & lookupTable) const { - if (gradient != 0) +#if JUCE_DEBUG + // trying to use the object without setting its co-ordinates? Have a careful read of + // the comments for the constructors. + jassert (point1.getX() != 987654.0f); +#endif + + const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), + 3 * (int) point1.transformedBy (transform) + .getDistanceFrom (point2.transformedBy (transform))); + lookupTable.malloc (numEntries); + + if (colours.size() >= 2) { - *gradient = newGradient; + jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 + + PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); + int index = 0; + + for (int j = 1; j < colours.size(); ++j) + { + const ColourPoint& p = colours.getReference (j); + const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; + const PixelARGB pix2 (p.colour.getPixelARGB()); + + for (int i = 0; i < numToDo; ++i) + { + jassert (index >= 0 && index < numEntries); + + lookupTable[index] = pix1; + lookupTable[index].tween (pix2, (i << 8) / numToDo); + ++index; + } + + pix1 = pix2; + } + + while (index < numEntries) + lookupTable [index++] = pix1; } else { - image = Image(); - gradient = new ColourGradient (newGradient); - colour = Colours::black; + jassertfalse; // no colours specified! } + + return numEntries; } -void FillType::setTiledImage (const Image& image_, const AffineTransform& transform_) throw() +bool ColourGradient::isOpaque() const throw() { - gradient = 0; - image = image_; - transform = transform_; - colour = Colours::black; + for (int i = 0; i < colours.size(); ++i) + if (! colours.getReference(i).colour.isOpaque()) + return false; + + return true; } -void FillType::setOpacity (const float newOpacity) throw() +bool ColourGradient::isInvisible() const throw() { - colour = colour.withAlpha (newOpacity); + for (int i = 0; i < colours.size(); ++i) + if (! colours.getReference(i).colour.isTransparent()) + return false; + + return true; } -bool FillType::isInvisible() const throw() -{ - return colour.isTransparent() || (gradient != 0 && gradient->isInvisible()); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_ColourGradient.cpp ***/ + + +/*** Start of inlined file: juce_Colours.cpp ***/ +BEGIN_JUCE_NAMESPACE + +const Colour Colours::transparentBlack (0); +const Colour Colours::transparentWhite (0x00ffffff); + +const Colour Colours::aliceblue (0xfff0f8ff); +const Colour Colours::antiquewhite (0xfffaebd7); +const Colour Colours::aqua (0xff00ffff); +const Colour Colours::aquamarine (0xff7fffd4); +const Colour Colours::azure (0xfff0ffff); +const Colour Colours::beige (0xfff5f5dc); +const Colour Colours::bisque (0xffffe4c4); +const Colour Colours::black (0xff000000); +const Colour Colours::blanchedalmond (0xffffebcd); +const Colour Colours::blue (0xff0000ff); +const Colour Colours::blueviolet (0xff8a2be2); +const Colour Colours::brown (0xffa52a2a); +const Colour Colours::burlywood (0xffdeb887); +const Colour Colours::cadetblue (0xff5f9ea0); +const Colour Colours::chartreuse (0xff7fff00); +const Colour Colours::chocolate (0xffd2691e); +const Colour Colours::coral (0xffff7f50); +const Colour Colours::cornflowerblue (0xff6495ed); +const Colour Colours::cornsilk (0xfffff8dc); +const Colour Colours::crimson (0xffdc143c); +const Colour Colours::cyan (0xff00ffff); +const Colour Colours::darkblue (0xff00008b); +const Colour Colours::darkcyan (0xff008b8b); +const Colour Colours::darkgoldenrod (0xffb8860b); +const Colour Colours::darkgrey (0xff555555); +const Colour Colours::darkgreen (0xff006400); +const Colour Colours::darkkhaki (0xffbdb76b); +const Colour Colours::darkmagenta (0xff8b008b); +const Colour Colours::darkolivegreen (0xff556b2f); +const Colour Colours::darkorange (0xffff8c00); +const Colour Colours::darkorchid (0xff9932cc); +const Colour Colours::darkred (0xff8b0000); +const Colour Colours::darksalmon (0xffe9967a); +const Colour Colours::darkseagreen (0xff8fbc8f); +const Colour Colours::darkslateblue (0xff483d8b); +const Colour Colours::darkslategrey (0xff2f4f4f); +const Colour Colours::darkturquoise (0xff00ced1); +const Colour Colours::darkviolet (0xff9400d3); +const Colour Colours::deeppink (0xffff1493); +const Colour Colours::deepskyblue (0xff00bfff); +const Colour Colours::dimgrey (0xff696969); +const Colour Colours::dodgerblue (0xff1e90ff); +const Colour Colours::firebrick (0xffb22222); +const Colour Colours::floralwhite (0xfffffaf0); +const Colour Colours::forestgreen (0xff228b22); +const Colour Colours::fuchsia (0xffff00ff); +const Colour Colours::gainsboro (0xffdcdcdc); +const Colour Colours::gold (0xffffd700); +const Colour Colours::goldenrod (0xffdaa520); +const Colour Colours::grey (0xff808080); +const Colour Colours::green (0xff008000); +const Colour Colours::greenyellow (0xffadff2f); +const Colour Colours::honeydew (0xfff0fff0); +const Colour Colours::hotpink (0xffff69b4); +const Colour Colours::indianred (0xffcd5c5c); +const Colour Colours::indigo (0xff4b0082); +const Colour Colours::ivory (0xfffffff0); +const Colour Colours::khaki (0xfff0e68c); +const Colour Colours::lavender (0xffe6e6fa); +const Colour Colours::lavenderblush (0xfffff0f5); +const Colour Colours::lemonchiffon (0xfffffacd); +const Colour Colours::lightblue (0xffadd8e6); +const Colour Colours::lightcoral (0xfff08080); +const Colour Colours::lightcyan (0xffe0ffff); +const Colour Colours::lightgoldenrodyellow (0xfffafad2); +const Colour Colours::lightgreen (0xff90ee90); +const Colour Colours::lightgrey (0xffd3d3d3); +const Colour Colours::lightpink (0xffffb6c1); +const Colour Colours::lightsalmon (0xffffa07a); +const Colour Colours::lightseagreen (0xff20b2aa); +const Colour Colours::lightskyblue (0xff87cefa); +const Colour Colours::lightslategrey (0xff778899); +const Colour Colours::lightsteelblue (0xffb0c4de); +const Colour Colours::lightyellow (0xffffffe0); +const Colour Colours::lime (0xff00ff00); +const Colour Colours::limegreen (0xff32cd32); +const Colour Colours::linen (0xfffaf0e6); +const Colour Colours::magenta (0xffff00ff); +const Colour Colours::maroon (0xff800000); +const Colour Colours::mediumaquamarine (0xff66cdaa); +const Colour Colours::mediumblue (0xff0000cd); +const Colour Colours::mediumorchid (0xffba55d3); +const Colour Colours::mediumpurple (0xff9370db); +const Colour Colours::mediumseagreen (0xff3cb371); +const Colour Colours::mediumslateblue (0xff7b68ee); +const Colour Colours::mediumspringgreen (0xff00fa9a); +const Colour Colours::mediumturquoise (0xff48d1cc); +const Colour Colours::mediumvioletred (0xffc71585); +const Colour Colours::midnightblue (0xff191970); +const Colour Colours::mintcream (0xfff5fffa); +const Colour Colours::mistyrose (0xffffe4e1); +const Colour Colours::navajowhite (0xffffdead); +const Colour Colours::navy (0xff000080); +const Colour Colours::oldlace (0xfffdf5e6); +const Colour Colours::olive (0xff808000); +const Colour Colours::olivedrab (0xff6b8e23); +const Colour Colours::orange (0xffffa500); +const Colour Colours::orangered (0xffff4500); +const Colour Colours::orchid (0xffda70d6); +const Colour Colours::palegoldenrod (0xffeee8aa); +const Colour Colours::palegreen (0xff98fb98); +const Colour Colours::paleturquoise (0xffafeeee); +const Colour Colours::palevioletred (0xffdb7093); +const Colour Colours::papayawhip (0xffffefd5); +const Colour Colours::peachpuff (0xffffdab9); +const Colour Colours::peru (0xffcd853f); +const Colour Colours::pink (0xffffc0cb); +const Colour Colours::plum (0xffdda0dd); +const Colour Colours::powderblue (0xffb0e0e6); +const Colour Colours::purple (0xff800080); +const Colour Colours::red (0xffff0000); +const Colour Colours::rosybrown (0xffbc8f8f); +const Colour Colours::royalblue (0xff4169e1); +const Colour Colours::saddlebrown (0xff8b4513); +const Colour Colours::salmon (0xfffa8072); +const Colour Colours::sandybrown (0xfff4a460); +const Colour Colours::seagreen (0xff2e8b57); +const Colour Colours::seashell (0xfffff5ee); +const Colour Colours::sienna (0xffa0522d); +const Colour Colours::silver (0xffc0c0c0); +const Colour Colours::skyblue (0xff87ceeb); +const Colour Colours::slateblue (0xff6a5acd); +const Colour Colours::slategrey (0xff708090); +const Colour Colours::snow (0xfffffafa); +const Colour Colours::springgreen (0xff00ff7f); +const Colour Colours::steelblue (0xff4682b4); +const Colour Colours::tan (0xffd2b48c); +const Colour Colours::teal (0xff008080); +const Colour Colours::thistle (0xffd8bfd8); +const Colour Colours::tomato (0xffff6347); +const Colour Colours::turquoise (0xff40e0d0); +const Colour Colours::violet (0xffee82ee); +const Colour Colours::wheat (0xfff5deb3); +const Colour Colours::white (0xffffffff); +const Colour Colours::whitesmoke (0xfff5f5f5); +const Colour Colours::yellow (0xffffff00); +const Colour Colours::yellowgreen (0xff9acd32); + +const Colour Colours::findColourForName (const String& colourName, + const Colour& defaultColour) +{ + static const int presets[] = + { + // (first value is the string's hashcode, second is ARGB) + + 0x05978fff, 0xff000000, /* black */ + 0x06bdcc29, 0xffffffff, /* white */ + 0x002e305a, 0xff0000ff, /* blue */ + 0x00308adf, 0xff808080, /* grey */ + 0x05e0cf03, 0xff008000, /* green */ + 0x0001b891, 0xffff0000, /* red */ + 0xd43c6474, 0xffffff00, /* yellow */ + 0x620886da, 0xfff0f8ff, /* aliceblue */ + 0x20a2676a, 0xfffaebd7, /* antiquewhite */ + 0x002dcebc, 0xff00ffff, /* aqua */ + 0x46bb5f7e, 0xff7fffd4, /* aquamarine */ + 0x0590228f, 0xfff0ffff, /* azure */ + 0x05947fe4, 0xfff5f5dc, /* beige */ + 0xad388e35, 0xffffe4c4, /* bisque */ + 0x00674f7e, 0xffffebcd, /* blanchedalmond */ + 0x39129959, 0xff8a2be2, /* blueviolet */ + 0x059a8136, 0xffa52a2a, /* brown */ + 0x89cea8f9, 0xffdeb887, /* burlywood */ + 0x0fa260cf, 0xff5f9ea0, /* cadetblue */ + 0x6b748956, 0xff7fff00, /* chartreuse */ + 0x2903623c, 0xffd2691e, /* chocolate */ + 0x05a74431, 0xffff7f50, /* coral */ + 0x618d42dd, 0xff6495ed, /* cornflowerblue */ + 0xe4b479fd, 0xfffff8dc, /* cornsilk */ + 0x3d8c4edf, 0xffdc143c, /* crimson */ + 0x002ed323, 0xff00ffff, /* cyan */ + 0x67cc74d0, 0xff00008b, /* darkblue */ + 0x67cd1799, 0xff008b8b, /* darkcyan */ + 0x31bbd168, 0xffb8860b, /* darkgoldenrod */ + 0x67cecf55, 0xff555555, /* darkgrey */ + 0x920b194d, 0xff006400, /* darkgreen */ + 0x923edd4c, 0xffbdb76b, /* darkkhaki */ + 0x5c293873, 0xff8b008b, /* darkmagenta */ + 0x6b6671fe, 0xff556b2f, /* darkolivegreen */ + 0xbcfd2524, 0xffff8c00, /* darkorange */ + 0xbcfdf799, 0xff9932cc, /* darkorchid */ + 0x55ee0d5b, 0xff8b0000, /* darkred */ + 0xc2e5f564, 0xffe9967a, /* darksalmon */ + 0x61be858a, 0xff8fbc8f, /* darkseagreen */ + 0xc2b0f2bd, 0xff483d8b, /* darkslateblue */ + 0xc2b34d42, 0xff2f4f4f, /* darkslategrey */ + 0x7cf2b06b, 0xff00ced1, /* darkturquoise */ + 0xc8769375, 0xff9400d3, /* darkviolet */ + 0x25832862, 0xffff1493, /* deeppink */ + 0xfcad568f, 0xff00bfff, /* deepskyblue */ + 0x634c8b67, 0xff696969, /* dimgrey */ + 0x45c1ce55, 0xff1e90ff, /* dodgerblue */ + 0xef19e3cb, 0xffb22222, /* firebrick */ + 0xb852b195, 0xfffffaf0, /* floralwhite */ + 0xd086fd06, 0xff228b22, /* forestgreen */ + 0xe106b6d7, 0xffff00ff, /* fuchsia */ + 0x7880d61e, 0xffdcdcdc, /* gainsboro */ + 0x00308060, 0xffffd700, /* gold */ + 0xb3b3bc1e, 0xffdaa520, /* goldenrod */ + 0xbab8a537, 0xffadff2f, /* greenyellow */ + 0xe4cacafb, 0xfff0fff0, /* honeydew */ + 0x41892743, 0xffff69b4, /* hotpink */ + 0xd5796f1a, 0xffcd5c5c, /* indianred */ + 0xb969fed2, 0xff4b0082, /* indigo */ + 0x05fef6a9, 0xfffffff0, /* ivory */ + 0x06149302, 0xfff0e68c, /* khaki */ + 0xad5a05c7, 0xffe6e6fa, /* lavender */ + 0x7c4d5b99, 0xfffff0f5, /* lavenderblush */ + 0x195756f0, 0xfffffacd, /* lemonchiffon */ + 0x28e4ea70, 0xffadd8e6, /* lightblue */ + 0xf3c7ccdb, 0xfff08080, /* lightcoral */ + 0x28e58d39, 0xffe0ffff, /* lightcyan */ + 0x21234e3c, 0xfffafad2, /* lightgoldenrodyellow */ + 0xf40157ad, 0xff90ee90, /* lightgreen */ + 0x28e744f5, 0xffd3d3d3, /* lightgrey */ + 0x28eb3b8c, 0xffffb6c1, /* lightpink */ + 0x9fb78304, 0xffffa07a, /* lightsalmon */ + 0x50632b2a, 0xff20b2aa, /* lightseagreen */ + 0x68fb7b25, 0xff87cefa, /* lightskyblue */ + 0xa8a35ba2, 0xff778899, /* lightslategrey */ + 0xa20d484f, 0xffb0c4de, /* lightsteelblue */ + 0xaa2cf10a, 0xffffffe0, /* lightyellow */ + 0x0032afd5, 0xff00ff00, /* lime */ + 0x607bbc4e, 0xff32cd32, /* limegreen */ + 0x06234efa, 0xfffaf0e6, /* linen */ + 0x316858a9, 0xffff00ff, /* magenta */ + 0xbf8ca470, 0xff800000, /* maroon */ + 0xbd58e0b3, 0xff66cdaa, /* mediumaquamarine */ + 0x967dfd4f, 0xff0000cd, /* mediumblue */ + 0x056f5c58, 0xffba55d3, /* mediumorchid */ + 0x07556b71, 0xff9370db, /* mediumpurple */ + 0x5369b689, 0xff3cb371, /* mediumseagreen */ + 0x066be19e, 0xff7b68ee, /* mediumslateblue */ + 0x3256b281, 0xff00fa9a, /* mediumspringgreen */ + 0xc0ad9f4c, 0xff48d1cc, /* mediumturquoise */ + 0x628e63dd, 0xffc71585, /* mediumvioletred */ + 0x168eb32a, 0xff191970, /* midnightblue */ + 0x4306b960, 0xfff5fffa, /* mintcream */ + 0x4cbc0e6b, 0xffffe4e1, /* mistyrose */ + 0xe97218a6, 0xffffdead, /* navajowhite */ + 0x00337bb6, 0xff000080, /* navy */ + 0xadd2d33e, 0xfffdf5e6, /* oldlace */ + 0x064ee1db, 0xff808000, /* olive */ + 0x9e33a98a, 0xff6b8e23, /* olivedrab */ + 0xc3de262e, 0xffffa500, /* orange */ + 0x58bebba3, 0xffff4500, /* orangered */ + 0xc3def8a3, 0xffda70d6, /* orchid */ + 0x28cb4834, 0xffeee8aa, /* palegoldenrod */ + 0x3d9dd619, 0xff98fb98, /* palegreen */ + 0x74022737, 0xffafeeee, /* paleturquoise */ + 0x15e2ebc8, 0xffdb7093, /* palevioletred */ + 0x5fd898e2, 0xffffefd5, /* papayawhip */ + 0x93e1b776, 0xffffdab9, /* peachpuff */ + 0x003472f8, 0xffcd853f, /* peru */ + 0x00348176, 0xffffc0cb, /* pink */ + 0x00348d94, 0xffdda0dd, /* plum */ + 0xd036be93, 0xffb0e0e6, /* powderblue */ + 0xc5c507bc, 0xff800080, /* purple */ + 0xa89d65b3, 0xffbc8f8f, /* rosybrown */ + 0xbd9413e1, 0xff4169e1, /* royalblue */ + 0xf456044f, 0xff8b4513, /* saddlebrown */ + 0xc9c6f66e, 0xfffa8072, /* salmon */ + 0x0bb131e1, 0xfff4a460, /* sandybrown */ + 0x34636c14, 0xff2e8b57, /* seagreen */ + 0x3507fb41, 0xfffff5ee, /* seashell */ + 0xca348772, 0xffa0522d, /* sienna */ + 0xca37d30d, 0xffc0c0c0, /* silver */ + 0x80da74fb, 0xff87ceeb, /* skyblue */ + 0x44a8dd73, 0xff6a5acd, /* slateblue */ + 0x44ab37f8, 0xff708090, /* slategrey */ + 0x0035f183, 0xfffffafa, /* snow */ + 0xd5440d16, 0xff00ff7f, /* springgreen */ + 0x3e1524a5, 0xff4682b4, /* steelblue */ + 0x0001bfa1, 0xffd2b48c, /* tan */ + 0x0036425c, 0xff008080, /* teal */ + 0xafc8858f, 0xffd8bfd8, /* thistle */ + 0xcc41600a, 0xffff6347, /* tomato */ + 0xfeea9b21, 0xff40e0d0, /* turquoise */ + 0xcf57947f, 0xffee82ee, /* violet */ + 0x06bdbae7, 0xfff5deb3, /* wheat */ + 0x10802ee6, 0xfff5f5f5, /* whitesmoke */ + 0xe1b5130f, 0xff9acd32 /* yellowgreen */ + }; + + const int hash = colourName.trim().toLowerCase().hashCode(); + + for (int i = 0; i < numElementsInArray (presets); i += 2) + if (presets [i] == hash) + return Colour (presets [i + 1]); + + return defaultColour; } END_JUCE_NAMESPACE -/*** End of inlined file: juce_FillType.cpp ***/ +/*** End of inlined file: juce_Colours.cpp ***/ -/*** Start of inlined file: juce_Graphics.cpp ***/ +/*** Start of inlined file: juce_EdgeTable.cpp ***/ BEGIN_JUCE_NAMESPACE -template -static bool areCoordsSensibleNumbers (Type x, Type y, Type w, Type h) -{ - const int maxVal = 0x3fffffff; - - return (int) x >= -maxVal && (int) x <= maxVal - && (int) y >= -maxVal && (int) y <= maxVal - && (int) w >= -maxVal && (int) w <= maxVal - && (int) h >= -maxVal && (int) h <= maxVal; -} - -LowLevelGraphicsContext::LowLevelGraphicsContext() -{ -} - -LowLevelGraphicsContext::~LowLevelGraphicsContext() -{ -} - -Graphics::Graphics (const Image& imageToDrawOnto) - : context (imageToDrawOnto.createLowLevelContext()), - contextToDelete (context), - saveStatePending (false) -{ -} +const int juce_edgeTableDefaultEdgesPerLine = 32; -Graphics::Graphics (LowLevelGraphicsContext* const internalContext) throw() - : context (internalContext), - saveStatePending (false) +EdgeTable::EdgeTable (const Rectangle& bounds_, + const Path& path, const AffineTransform& transform) + : bounds (bounds_), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), + needToCheckEmptinesss (true) { -} + table.malloc ((bounds.getHeight() + 1) * lineStrideElements); + int* t = table; -Graphics::~Graphics() -{ -} + for (int i = bounds.getHeight(); --i >= 0;) + { + *t = 0; + t += lineStrideElements; + } -void Graphics::resetToDefaultState() -{ - saveStateIfPending(); - context->setFill (FillType()); - context->setFont (Font()); - context->setInterpolationQuality (Graphics::mediumResamplingQuality); -} + const int topLimit = bounds.getY() << 8; + const int heightLimit = bounds.getHeight() << 8; + const int leftLimit = bounds.getX() << 8; + const int rightLimit = bounds.getRight() << 8; -bool Graphics::isVectorDevice() const -{ - return context->isVectorDevice(); -} + PathFlatteningIterator iter (path, transform); -bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) -{ - saveStateIfPending(); - return context->clipToRectangle (Rectangle (x, y, w, h)); -} + while (iter.next()) + { + int y1 = roundToInt (iter.y1 * 256.0f); + int y2 = roundToInt (iter.y2 * 256.0f); -bool Graphics::reduceClipRegion (const RectangleList& clipRegion) -{ - saveStateIfPending(); - return context->clipToRectangleList (clipRegion); -} + if (y1 != y2) + { + y1 -= topLimit; + y2 -= topLimit; -bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) -{ - saveStateIfPending(); - context->clipToPath (path, transform); - return ! context->isClipEmpty(); -} + const int startY = y1; + int direction = -1; -bool Graphics::reduceClipRegion (const Image& image, const Rectangle& sourceClipRegion, const AffineTransform& transform) -{ - saveStateIfPending(); - context->clipToImageAlpha (image, sourceClipRegion, transform); - return ! context->isClipEmpty(); -} + if (y1 > y2) + { + swapVariables (y1, y2); + direction = 1; + } -void Graphics::excludeClipRegion (const Rectangle& rectangleToExclude) -{ - saveStateIfPending(); - context->excludeClipRectangle (rectangleToExclude); -} + if (y1 < 0) + y1 = 0; -bool Graphics::isClipEmpty() const -{ - return context->isClipEmpty(); -} + if (y2 > heightLimit) + y2 = heightLimit; -const Rectangle Graphics::getClipBounds() const -{ - return context->getClipBounds(); -} + if (y1 < y2) + { + const double startX = 256.0f * iter.x1; + const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); + const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); -void Graphics::saveState() -{ - saveStateIfPending(); - saveStatePending = true; -} + do + { + const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); + int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); -void Graphics::restoreState() -{ - if (saveStatePending) - saveStatePending = false; - else - context->restoreState(); -} + if (x < leftLimit) + x = leftLimit; + else if (x >= rightLimit) + x = rightLimit - 1; -void Graphics::saveStateIfPending() -{ - if (saveStatePending) - { - saveStatePending = false; - context->saveState(); + addEdgePoint (x, y1 >> 8, direction * step); + y1 += step; + } + while (y1 < y2); + } + } } -} - -void Graphics::setOrigin (const int newOriginX, const int newOriginY) -{ - saveStateIfPending(); - context->setOrigin (newOriginX, newOriginY); -} - -bool Graphics::clipRegionIntersects (const Rectangle& area) const -{ - return context->clipRegionIntersects (area); -} - -void Graphics::setColour (const Colour& newColour) -{ - saveStateIfPending(); - context->setFill (newColour); -} - -void Graphics::setOpacity (const float newOpacity) -{ - saveStateIfPending(); - context->setOpacity (newOpacity); -} - -void Graphics::setGradientFill (const ColourGradient& gradient) -{ - setFillType (gradient); -} - -void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity) -{ - saveStateIfPending(); - context->setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY))); - context->setOpacity (opacity); -} - -void Graphics::setFillType (const FillType& newFill) -{ - saveStateIfPending(); - context->setFill (newFill); -} -void Graphics::setFont (const Font& newFont) -{ - saveStateIfPending(); - context->setFont (newFont); -} - -void Graphics::setFont (const float newFontHeight, const int newFontStyleFlags) -{ - saveStateIfPending(); - Font f (context->getFont()); - f.setSizeAndStyle (newFontHeight, newFontStyleFlags, 1.0f, 0); - context->setFont (f); + sanitiseLevels (path.isUsingNonZeroWinding()); } -const Font Graphics::getCurrentFont() const +EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) + : bounds (rectangleToAdd), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), + needToCheckEmptinesss (true) { - return context->getFont(); -} + table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); + table[0] = 0; -void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY) const -{ - if (text.isNotEmpty() - && startX < context->getClipBounds().getRight()) - { - GlyphArrangement arr; - arr.addLineOfText (context->getFont(), text, (float) startX, (float) baselineY); - arr.draw (*this); - } -} + const int x1 = rectangleToAdd.getX() << 8; + const int x2 = rectangleToAdd.getRight() << 8; -void Graphics::drawTextAsPath (const String& text, const AffineTransform& transform) const -{ - if (text.isNotEmpty()) + int* t = table; + for (int i = rectangleToAdd.getHeight(); --i >= 0;) { - GlyphArrangement arr; - arr.addLineOfText (context->getFont(), text, 0.0f, 0.0f); - arr.draw (*this, transform); + t[0] = 2; + t[1] = x1; + t[2] = 255; + t[3] = x2; + t[4] = 0; + t += lineStrideElements; } } -void Graphics::drawMultiLineText (const String& text, const int startX, const int baselineY, const int maximumLineWidth) const +EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) + : bounds (rectanglesToAdd.getBounds()), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), + needToCheckEmptinesss (true) { - if (text.isNotEmpty() - && startX < context->getClipBounds().getRight()) - { - GlyphArrangement arr; - arr.addJustifiedText (context->getFont(), text, - (float) startX, (float) baselineY, (float) maximumLineWidth, - Justification::left); - arr.draw (*this); - } -} + table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); -void Graphics::drawText (const String& text, - const int x, const int y, const int width, const int height, - const Justification& justificationType, - const bool useEllipsesIfTooBig) const -{ - if (text.isNotEmpty() && context->clipRegionIntersects (Rectangle (x, y, width, height))) + int* t = table; + for (int i = bounds.getHeight(); --i >= 0;) { - GlyphArrangement arr; - - arr.addCurtailedLineOfText (context->getFont(), text, - 0.0f, 0.0f, (float) width, - useEllipsesIfTooBig); - - arr.justifyGlyphs (0, arr.getNumGlyphs(), - (float) x, (float) y, (float) width, (float) height, - justificationType); - arr.draw (*this); + *t = 0; + t += lineStrideElements; } -} -void Graphics::drawFittedText (const String& text, - const int x, const int y, const int width, const int height, - const Justification& justification, - const int maximumNumberOfLines, - const float minimumHorizontalScale) const -{ - if (text.isNotEmpty() - && width > 0 && height > 0 - && context->clipRegionIntersects (Rectangle (x, y, width, height))) + for (RectangleList::Iterator iter (rectanglesToAdd); iter.next();) { - GlyphArrangement arr; + const Rectangle* const r = iter.getRectangle(); - arr.addFittedText (context->getFont(), text, - (float) x, (float) y, (float) width, (float) height, - justification, - maximumNumberOfLines, - minimumHorizontalScale); + const int x1 = r->getX() << 8; + const int x2 = r->getRight() << 8; + int y = r->getY() - bounds.getY(); - arr.draw (*this); + for (int j = r->getHeight(); --j >= 0;) + { + addEdgePoint (x1, y, 255); + addEdgePoint (x2, y, -255); + ++y; + } } -} - -void Graphics::fillRect (int x, int y, int width, int height) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - context->fillRect (Rectangle (x, y, width, height), false); -} - -void Graphics::fillRect (const Rectangle& r) const -{ - context->fillRect (r, false); + sanitiseLevels (true); } -void Graphics::fillRect (const float x, const float y, const float width, const float height) const +EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) + : bounds (Rectangle ((int) std::floor (rectangleToAdd.getX()), + roundToInt (rectangleToAdd.getY() * 256.0f) >> 8, + 2 + (int) rectangleToAdd.getWidth(), + 2 + (int) rectangleToAdd.getHeight())), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), + needToCheckEmptinesss (true) { - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - - Path p; - p.addRectangle (x, y, width, height); - fillPath (p); -} + jassert (! rectangleToAdd.isEmpty()); + table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); + table[0] = 0; -void Graphics::setPixel (int x, int y) const -{ - context->fillRect (Rectangle (x, y, 1, 1), false); -} + const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f); + const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f); -void Graphics::fillAll() const -{ - fillRect (context->getClipBounds()); -} + int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8); + jassert (y1 < 256); + int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8); -void Graphics::fillAll (const Colour& colourToUse) const -{ - if (! colourToUse.isTransparent()) + if (x2 <= x1 || y2 <= y1) { - const Rectangle clip (context->getClipBounds()); - - context->saveState(); - context->setFill (colourToUse); - context->fillRect (clip, false); - context->restoreState(); + bounds.setHeight (0); + return; } -} - -void Graphics::fillPath (const Path& path, const AffineTransform& transform) const -{ - if ((! context->isClipEmpty()) && ! path.isEmpty()) - context->fillPath (path, transform); -} - -void Graphics::strokePath (const Path& path, - const PathStrokeType& strokeType, - const AffineTransform& transform) const -{ - Path stroke; - strokeType.createStrokedPath (stroke, path, transform); - fillPath (stroke); -} - -void Graphics::drawRect (const int x, const int y, const int width, const int height, - const int lineThickness) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - - context->fillRect (Rectangle (x, y, width, lineThickness), false); - context->fillRect (Rectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2), false); - context->fillRect (Rectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2), false); - context->fillRect (Rectangle (x, y + height - lineThickness, width, lineThickness), false); -} - -void Graphics::drawRect (const float x, const float y, const float width, const float height, const float lineThickness) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - - Path p; - p.addRectangle (x, y, width, lineThickness); - p.addRectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2.0f); - p.addRectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2.0f); - p.addRectangle (x, y + height - lineThickness, width, lineThickness); - fillPath (p); -} - -void Graphics::drawRect (const Rectangle& r, const int lineThickness) const -{ - drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), lineThickness); -} -void Graphics::drawBevel (const int x, const int y, const int width, const int height, - const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour, - const bool useGradient, const bool sharpEdgeOnOutside) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); + int lineY = 0; + int* t = table; - if (clipRegionIntersects (Rectangle (x, y, width, height))) + if ((y1 >> 8) == (y2 >> 8)) { - context->saveState(); - - const float oldOpacity = 1.0f;//xxx state->colour.getFloatAlpha(); - const float ramp = oldOpacity / bevelThickness; + t[0] = 2; + t[1] = x1; + t[2] = y2 - y1; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + } + else + { + t[0] = 2; + t[1] = x1; + t[2] = 255 - (y1 & 255); + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; - for (int i = bevelThickness; --i >= 0;) + while (lineY < (y2 >> 8)) { - const float op = useGradient ? ramp * (sharpEdgeOnOutside ? bevelThickness - i : i) - : oldOpacity; - - context->setFill (topLeftColour.withMultipliedAlpha (op)); - context->fillRect (Rectangle (x + i, y + i, width - i * 2, 1), false); - context->setFill (topLeftColour.withMultipliedAlpha (op * 0.75f)); - context->fillRect (Rectangle (x + i, y + i + 1, 1, height - i * 2 - 2), false); - context->setFill (bottomRightColour.withMultipliedAlpha (op)); - context->fillRect (Rectangle (x + i, y + height - i - 1, width - i * 2, 1), false); - context->setFill (bottomRightColour.withMultipliedAlpha (op * 0.75f)); - context->fillRect (Rectangle (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2), false); + t[0] = 2; + t[1] = x1; + t[2] = 255; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; } - context->restoreState(); - } -} - -void Graphics::fillEllipse (const float x, const float y, const float width, const float height) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - - Path p; - p.addEllipse (x, y, width, height); - fillPath (p); -} - -void Graphics::drawEllipse (const float x, const float y, const float width, const float height, - const float lineThickness) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); - - Path p; - p.addEllipse (x, y, width, height); - strokePath (p, PathStrokeType (lineThickness)); -} - -void Graphics::fillRoundedRectangle (const float x, const float y, const float width, const float height, const float cornerSize) const -{ - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); + jassert (lineY < bounds.getHeight()); + t[0] = 2; + t[1] = x1; + t[2] = y2 & 255; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + } - Path p; - p.addRoundedRectangle (x, y, width, height, cornerSize); - fillPath (p); + while (lineY < bounds.getHeight()) + { + t[0] = 0; + t += lineStrideElements; + ++lineY; + } } -void Graphics::fillRoundedRectangle (const Rectangle& r, const float cornerSize) const +EdgeTable::EdgeTable (const EdgeTable& other) { - fillRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize); + operator= (other); } -void Graphics::drawRoundedRectangle (const float x, const float y, const float width, const float height, - const float cornerSize, const float lineThickness) const +EdgeTable& EdgeTable::operator= (const EdgeTable& other) { - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (x, y, width, height)); + bounds = other.bounds; + maxEdgesPerLine = other.maxEdgesPerLine; + lineStrideElements = other.lineStrideElements; + needToCheckEmptinesss = other.needToCheckEmptinesss; - Path p; - p.addRoundedRectangle (x, y, width, height, cornerSize); - strokePath (p, PathStrokeType (lineThickness)); + table.malloc (jmax (1, bounds.getHeight()) * lineStrideElements); + copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); + return *this; } -void Graphics::drawRoundedRectangle (const Rectangle& r, const float cornerSize, const float lineThickness) const +EdgeTable::~EdgeTable() { - drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness); } -void Graphics::drawArrow (const Line& line, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const +void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) throw() { - Path p; - p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); - fillPath (p); + while (--numLines >= 0) + { + memcpy (dest, src, (src[0] * 2 + 1) * sizeof (int)); + src += srcLineStride; + dest += destLineStride; + } } -void Graphics::fillCheckerBoard (const Rectangle& area, - const int checkWidth, const int checkHeight, - const Colour& colour1, const Colour& colour2) const +void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) throw() { - jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! + // Convert the table from relative windings to absolute levels.. + int* lineStart = table; - if (checkWidth > 0 && checkHeight > 0) + for (int i = bounds.getHeight(); --i >= 0;) { - context->saveState(); + int* line = lineStart; + lineStart += lineStrideElements; - if (colour1 == colour2) + int num = *line; + if (num == 0) + continue; + + int level = 0; + + if (useNonZeroWinding) { - context->setFill (colour1); - context->fillRect (area, false); + while (--num > 0) + { + line += 2; + level += *line; + int corrected = abs (level); + if (corrected >> 8) + corrected = 255; + + *line = corrected; + } } else { - const Rectangle clipped (context->getClipBounds().getIntersection (area)); - - if (! clipped.isEmpty()) + while (--num > 0) { - context->clipToRectangle (clipped); - - const int checkNumX = (clipped.getX() - area.getX()) / checkWidth; - const int checkNumY = (clipped.getY() - area.getY()) / checkHeight; - const int startX = area.getX() + checkNumX * checkWidth; - const int startY = area.getY() + checkNumY * checkHeight; - const int right = clipped.getRight(); - const int bottom = clipped.getBottom(); - - for (int i = 0; i < 2; ++i) + line += 2; + level += *line; + int corrected = abs (level); + if (corrected >> 8) { - context->setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2); - - int cy = i; - for (int y = startY; y < bottom; y += checkHeight) - for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2) - context->fillRect (Rectangle (x, y, checkWidth, checkHeight), false); + corrected &= 511; + if (corrected >> 8) + corrected = 511 - corrected; } + + *line = corrected; } } - context->restoreState(); + line[2] = 0; // force the last level to 0, just in case something went wrong in creating the table } } -void Graphics::drawVerticalLine (const int x, float top, float bottom) const +void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) throw() { - context->drawVerticalLine (x, top, bottom); -} + if (newNumEdgesPerLine != maxEdgesPerLine) + { + maxEdgesPerLine = newNumEdgesPerLine; -void Graphics::drawHorizontalLine (const int y, float left, float right) const -{ - context->drawHorizontalLine (y, left, right); -} + jassert (bounds.getHeight() > 0); + const int newLineStrideElements = maxEdgesPerLine * 2 + 1; -void Graphics::drawLine (float x1, float y1, float x2, float y2) const -{ - context->drawLine (Line (x1, y1, x2, y2)); -} + HeapBlock newTable (bounds.getHeight() * newLineStrideElements); -void Graphics::drawLine (const float startX, const float startY, - const float endX, const float endY, - const float lineThickness) const -{ - drawLine (Line (startX, startY, endX, endY),lineThickness); -} + copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); -void Graphics::drawLine (const Line& line) const -{ - drawLine (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY()); + table.swapWith (newTable); + lineStrideElements = newLineStrideElements; + } } -void Graphics::drawLine (const Line& line, const float lineThickness) const +void EdgeTable::optimiseTable() throw() { - Path p; - p.addLineSegment (line, lineThickness); - fillPath (p); + int maxLineElements = 0; + + for (int i = bounds.getHeight(); --i >= 0;) + maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]); + + remapTableForNumEdges (maxLineElements); } -void Graphics::drawDashedLine (const float startX, const float startY, - const float endX, const float endY, - const float* const dashLengths, - const int numDashLengths, - const float lineThickness) const +void EdgeTable::addEdgePoint (const int x, const int y, const int winding) throw() { - const double dx = endX - startX; - const double dy = endY - startY; - const double totalLen = juce_hypot (dx, dy); - - if (totalLen >= 0.5) - { - const double onePixAlpha = 1.0 / totalLen; + jassert (y >= 0 && y < bounds.getHeight()); - double alpha = 0.0; - float x = startX; - float y = startY; - int n = 0; + int* line = table + lineStrideElements * y; + const int numPoints = line[0]; + int n = numPoints << 1; - while (alpha < 1.0f) + if (n > 0) + { + while (n > 0) { - alpha = jmin (1.0, alpha + dashLengths[n++] * onePixAlpha); - n = n % numDashLengths; - - const float oldX = x; - const float oldY = y; - - x = (float) (startX + dx * alpha); - y = (float) (startY + dy * alpha); + const int cx = line [n - 1]; - if ((n & 1) != 0) + if (cx <= x) { - if (lineThickness != 1.0f) - drawLine (oldX, oldY, x, y, lineThickness); - else - drawLine (oldX, oldY, x, y); + if (cx == x) + { + line [n] += winding; + return; + } + + break; } + + n -= 2; } - } -} -void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) -{ - saveStateIfPending(); - context->setInterpolationQuality (newQuality); -} + if (numPoints >= maxEdgesPerLine) + { + remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); + jassert (numPoints < maxEdgesPerLine); + line = table + lineStrideElements * y; + } -void Graphics::drawImageAt (const Image& imageToDraw, - const int topLeftX, const int topLeftY, - const bool fillAlphaChannelWithCurrentBrush) const -{ - const int imageW = imageToDraw.getWidth(); - const int imageH = imageToDraw.getHeight(); + memmove (line + (n + 3), line + (n + 1), sizeof (int) * ((numPoints << 1) - n)); + } - drawImage (imageToDraw, - topLeftX, topLeftY, imageW, imageH, - 0, 0, imageW, imageH, - fillAlphaChannelWithCurrentBrush); + line [n + 1] = x; + line [n + 2] = winding; + line[0]++; } -void Graphics::drawImageWithin (const Image& imageToDraw, - const int destX, const int destY, - const int destW, const int destH, - const RectanglePlacement& placementWithinTarget, - const bool fillAlphaChannelWithCurrentBrush) const +void EdgeTable::translate (float dx, const int dy) throw() { - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (destX, destY, destW, destH)); + bounds.translate ((int) std::floor (dx), dy); - if (imageToDraw.isValid()) + int* lineStart = table; + const int intDx = (int) (dx * 256.0f); + + for (int i = bounds.getHeight(); --i >= 0;) { - const int imageW = imageToDraw.getWidth(); - const int imageH = imageToDraw.getHeight(); + int* line = lineStart; + lineStart += lineStrideElements; + int num = *line++; - if (imageW > 0 && imageH > 0) + while (--num >= 0) { - double newX = 0.0, newY = 0.0; - double newW = imageW; - double newH = imageH; - - placementWithinTarget.applyTo (newX, newY, newW, newH, - destX, destY, destW, destH); - - if (newW > 0 && newH > 0) - { - drawImage (imageToDraw, - roundToInt (newX), roundToInt (newY), - roundToInt (newW), roundToInt (newH), - 0, 0, imageW, imageH, - fillAlphaChannelWithCurrentBrush); - } + *line += intDx; + line += 2; } } } -void Graphics::drawImage (const Image& imageToDraw, - int dx, int dy, int dw, int dh, - int sx, int sy, int sw, int sh, - const bool fillAlphaChannelWithCurrentBrush) const +void EdgeTable::intersectWithEdgeTableLine (const int y, const int* otherLine) throw() { - // passing in a silly number can cause maths problems in rendering! - jassert (areCoordsSensibleNumbers (dx, dy, dw, dh)); - jassert (areCoordsSensibleNumbers (sx, sy, sw, sh)); + jassert (y >= 0 && y < bounds.getHeight()); - if (imageToDraw.isValid() && context->clipRegionIntersects (Rectangle (dx, dy, dw, dh))) + int* dest = table + lineStrideElements * y; + if (dest[0] == 0) + return; + + int otherNumPoints = *otherLine; + if (otherNumPoints == 0) { - drawImageTransformed (imageToDraw, Rectangle (sx, sy, sw, sh), - AffineTransform::scale (dw / (float) sw, dh / (float) sh) - .translated ((float) dx, (float) dy), - fillAlphaChannelWithCurrentBrush); + *dest = 0; + return; } -} -void Graphics::drawImageTransformed (const Image& imageToDraw, - const Rectangle& imageSubRegion, - const AffineTransform& transform, - const bool fillAlphaChannelWithCurrentBrush) const -{ - if (imageToDraw.isValid() && ! context->isClipEmpty()) + const int right = bounds.getRight() << 8; + + // optimise for the common case where our line lies entirely within a + // single pair of points, as happens when clipping to a simple rect. + if (otherNumPoints == 2 && otherLine[2] >= 255) { - const Rectangle srcClip (imageSubRegion.getIntersection (imageToDraw.getBounds())); + clipEdgeTableLineToRange (dest, otherLine[1], jmin (right, otherLine[3])); + return; + } - if (fillAlphaChannelWithCurrentBrush) + ++otherLine; + const size_t lineSizeBytes = (dest[0] * 2 + 1) * sizeof (int); + int* temp = (int*) alloca (lineSizeBytes); + memcpy (temp, dest, lineSizeBytes); + + const int* src1 = temp; + int srcNum1 = *src1++; + int x1 = *src1++; + + const int* src2 = otherLine; + int srcNum2 = otherNumPoints; + int x2 = *src2++; + + int destIndex = 0, destTotal = 0; + int level1 = 0, level2 = 0; + int lastX = std::numeric_limits::min(), lastLevel = 0; + + while (srcNum1 > 0 && srcNum2 > 0) + { + int nextX; + + if (x1 < x2) { - context->saveState(); - context->clipToImageAlpha (imageToDraw, srcClip, transform); - fillAll(); - context->restoreState(); + nextX = x1; + level1 = *src1++; + x1 = *src1++; + --srcNum1; + } + else if (x1 == x2) + { + nextX = x1; + level1 = *src1++; + level2 = *src2++; + x1 = *src1++; + x2 = *src2++; + --srcNum1; + --srcNum2; } else { - context->drawImage (imageToDraw, srcClip, transform, false); + nextX = x2; + level2 = *src2++; + x2 = *src2++; + --srcNum2; } - } -} -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Graphics.cpp ***/ + if (nextX > lastX) + { + if (nextX >= right) + break; + lastX = nextX; -/*** Start of inlined file: juce_Justification.cpp ***/ -BEGIN_JUCE_NAMESPACE + const int nextLevel = (level1 * (level2 + 1)) >> 8; + jassert (((unsigned int) nextLevel) < (unsigned int) 256); -Justification::Justification (const Justification& other) throw() - : flags (other.flags) -{ -} + if (nextLevel != lastLevel) + { + if (destTotal >= maxEdgesPerLine) + { + dest[0] = destTotal; + remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); + dest = table + lineStrideElements * y; + } -Justification& Justification::operator= (const Justification& other) throw() -{ - flags = other.flags; - return *this; -} + ++destTotal; + lastLevel = nextLevel; + dest[++destIndex] = nextX; + dest[++destIndex] = nextLevel; + } + } + } -int Justification::getOnlyVerticalFlags() const throw() -{ - return flags & (top | bottom | verticallyCentred); -} + if (lastLevel > 0) + { + if (destTotal >= maxEdgesPerLine) + { + dest[0] = destTotal; + remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); + dest = table + lineStrideElements * y; + } -int Justification::getOnlyHorizontalFlags() const throw() -{ - return flags & (left | right | horizontallyCentred | horizontallyJustified); -} + ++destTotal; + dest[++destIndex] = right; + dest[++destIndex] = 0; + } -void Justification::applyToRectangle (int& x, int& y, - const int w, const int h, - const int spaceX, const int spaceY, - const int spaceW, const int spaceH) const throw() -{ - if ((flags & horizontallyCentred) != 0) - x = spaceX + ((spaceW - w) >> 1); - else if ((flags & right) != 0) - x = spaceX + spaceW - w; - else - x = spaceX; + dest[0] = destTotal; - if ((flags & verticallyCentred) != 0) - y = spaceY + ((spaceH - h) >> 1); - else if ((flags & bottom) != 0) - y = spaceY + spaceH - h; - else - y = spaceY; -} +#if JUCE_DEBUG + int last = std::numeric_limits::min(); + for (int i = 0; i < dest[0]; ++i) + { + jassert (dest[i * 2 + 1] > last); + last = dest[i * 2 + 1]; + } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Justification.cpp ***/ + jassert (dest [dest[0] * 2] == 0); +#endif +} +void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) throw() +{ + int* lastItem = dest + (dest[0] * 2 - 1); -/*** Start of inlined file: juce_LowLevelGraphicsPostScriptRenderer.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (x2 < lastItem[0]) + { + if (x2 <= dest[1]) + { + dest[0] = 0; + return; + } -// this will throw an assertion if you try to draw something that's not -// possible in postscript -#define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 + while (x2 < lastItem[-2]) + { + --(dest[0]); + lastItem -= 2; + } -#if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS - #define notPossibleInPostscriptAssert jassertfalse -#else - #define notPossibleInPostscriptAssert -#endif + lastItem[0] = x2; + lastItem[1] = 0; + } -LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, - const String& documentTitle, - const int totalWidth_, - const int totalHeight_) - : out (resultingPostScript), - totalWidth (totalWidth_), - totalHeight (totalHeight_), - needToClip (true) -{ - stateStack.add (new SavedState()); - stateStack.getLast()->clip = Rectangle (totalWidth_, totalHeight_); + if (x1 > dest[1]) + { + while (lastItem[0] > x1) + lastItem -= 2; - const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); + const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2; - out << "%!PS-Adobe-3.0 EPSF-3.0" - "\n%%BoundingBox: 0 0 600 824" - "\n%%Pages: 0" - "\n%%Creator: Raw Material Software JUCE" - "\n%%Title: " << documentTitle << - "\n%%CreationDate: none" - "\n%%LanguageLevel: 2" - "\n%%EndComments" - "\n%%BeginProlog" - "\n%%BeginResource: JRes" - "\n/bd {bind def} bind def" - "\n/c {setrgbcolor} bd" - "\n/m {moveto} bd" - "\n/l {lineto} bd" - "\n/rl {rlineto} bd" - "\n/ct {curveto} bd" - "\n/cp {closepath} bd" - "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" - "\n/doclip {initclip newpath} bd" - "\n/endclip {clip newpath} bd" - "\n%%EndResource" - "\n%%EndProlog" - "\n%%BeginSetup" - "\n%%EndSetup" - "\n%%Page: 1 1" - "\n%%BeginPageSetup" - "\n%%EndPageSetup\n\n" - << "40 800 translate\n" - << scale << ' ' << scale << " scale\n\n"; -} + if (itemsRemoved > 0) + { + dest[0] -= itemsRemoved; + memmove (dest + 1, lastItem, dest[0] * (sizeof (int) * 2)); + } -LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() -{ + dest[1] = x1; + } } -bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const +void EdgeTable::clipToRectangle (const Rectangle& r) throw() { - return true; -} + const Rectangle clipped (r.getIntersection (bounds)); -void LowLevelGraphicsPostScriptRenderer::setOrigin (int x, int y) -{ - if (x != 0 || y != 0) + if (clipped.isEmpty()) { - stateStack.getLast()->xOffset += x; - stateStack.getLast()->yOffset += y; - needToClip = true; + needToCheckEmptinesss = false; + bounds.setHeight (0); } -} - -bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle& r) -{ - needToClip = true; - return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); -} + else + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); -bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList& clipRegion) -{ - needToClip = true; - return stateStack.getLast()->clip.clipTo (clipRegion); -} + if (bottom < bounds.getHeight()) + bounds.setHeight (bottom); -void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle& r) -{ - needToClip = true; - stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); -} + if (clipped.getRight() < bounds.getRight()) + bounds.setRight (clipped.getRight()); -void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform) -{ - writeClip(); + for (int i = top; --i >= 0;) + table [lineStrideElements * i] = 0; - Path p (path); - p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); - writePath (p); - out << "clip\n"; -} + if (clipped.getX() > bounds.getX()) + { + const int x1 = clipped.getX() << 8; + const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8; + int* line = table + lineStrideElements * top; -void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const Rectangle& /*srcClip*/, const AffineTransform& /*transform*/) -{ - needToClip = true; - jassertfalse; // xxx -} + for (int i = bottom - top; --i >= 0;) + { + if (line[0] != 0) + clipEdgeTableLineToRange (line, x1, x2); -bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle& r) -{ - return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); -} + line += lineStrideElements; + } + } -const Rectangle LowLevelGraphicsPostScriptRenderer::getClipBounds() const -{ - return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset, - -stateStack.getLast()->yOffset); + needToCheckEmptinesss = true; + } } -bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const +void EdgeTable::excludeRectangle (const Rectangle& r) throw() { - return stateStack.getLast()->clip.isEmpty(); -} + const Rectangle clipped (r.getIntersection (bounds)); -LowLevelGraphicsPostScriptRenderer::SavedState::SavedState() - : xOffset (0), - yOffset (0) -{ -} + if (! clipped.isEmpty()) + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); -LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() -{ -} + //XXX optimise here by shortening the table if it fills top or bottom -void LowLevelGraphicsPostScriptRenderer::saveState() -{ - stateStack.add (new SavedState (*stateStack.getLast())); -} + const int rectLine[] = { 4, std::numeric_limits::min(), 255, + clipped.getX() << 8, 0, + clipped.getRight() << 8, 255, + std::numeric_limits::max(), 0 }; -void LowLevelGraphicsPostScriptRenderer::restoreState() -{ - jassert (stateStack.size() > 0); + for (int i = top; i < bottom; ++i) + intersectWithEdgeTableLine (i, rectLine); - if (stateStack.size() > 0) - stateStack.removeLast(); + needToCheckEmptinesss = true; + } } -void LowLevelGraphicsPostScriptRenderer::writeClip() +void EdgeTable::clipToEdgeTable (const EdgeTable& other) { - if (needToClip) + const Rectangle clipped (other.bounds.getIntersection (bounds)); + + if (clipped.isEmpty()) { - needToClip = false; + needToCheckEmptinesss = false; + bounds.setHeight (0); + } + else + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); - out << "doclip "; + if (bottom < bounds.getHeight()) + bounds.setHeight (bottom); - int itemsOnLine = 0; + if (clipped.getRight() < bounds.getRight()) + bounds.setRight (clipped.getRight()); - for (RectangleList::Iterator i (stateStack.getLast()->clip); i.next();) - { - if (++itemsOnLine == 6) - { - itemsOnLine = 0; - out << '\n'; - } + int i = 0; + for (i = top; --i >= 0;) + table [lineStrideElements * i] = 0; - const Rectangle& r = *i.getRectangle(); + const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); - out << r.getX() << ' ' << -r.getY() << ' ' - << r.getWidth() << ' ' << -r.getHeight() << " pr "; + for (i = top; i < bottom; ++i) + { + intersectWithEdgeTableLine (i, otherLine); + otherLine += other.lineStrideElements; } - out << "endclip\n"; + needToCheckEmptinesss = true; } } -void LowLevelGraphicsPostScriptRenderer::writeColour (const Colour& colour) +void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) throw() { - Colour c (Colours::white.overlaidWith (colour)); - - if (lastColour != c) - { - lastColour = c; - - out << String (c.getFloatRed(), 3) << ' ' - << String (c.getFloatGreen(), 3) << ' ' - << String (c.getFloatBlue(), 3) << " c\n"; - } -} + y -= bounds.getY(); -void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const -{ - out << String (x, 2) << ' ' - << String (-y, 2) << ' '; -} + if (y < 0 || y >= bounds.getHeight()) + return; -void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const -{ - out << "newpath "; + needToCheckEmptinesss = true; - float lastX = 0.0f; - float lastY = 0.0f; - int itemsOnLine = 0; + if (numPixels <= 0) + { + table [lineStrideElements * y] = 0; + return; + } - Path::Iterator i (path); + int* tempLine = (int*) alloca ((numPixels * 2 + 4) * sizeof (int)); + int destIndex = 0, lastLevel = 0; - while (i.next()) + while (--numPixels >= 0) { - if (++itemsOnLine == 4) + const int alpha = *mask; + mask += maskStride; + + if (alpha != lastLevel) { - itemsOnLine = 0; - out << '\n'; + tempLine[++destIndex] = (x << 8); + tempLine[++destIndex] = alpha; + lastLevel = alpha; } - switch (i.elementType) - { - case Path::Iterator::startNewSubPath: - writeXY (i.x1, i.y1); - lastX = i.x1; - lastY = i.y1; - out << "m "; - break; + ++x; + } - case Path::Iterator::lineTo: - writeXY (i.x1, i.y1); - lastX = i.x1; - lastY = i.y1; - out << "l "; - break; + if (lastLevel > 0) + { + tempLine[++destIndex] = (x << 8); + tempLine[++destIndex] = 0; + } - case Path::Iterator::quadraticTo: - { - const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; - const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; - const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; - const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; + tempLine[0] = destIndex >> 1; - writeXY (cp1x, cp1y); - writeXY (cp2x, cp2y); - writeXY (i.x2, i.y2); - out << "ct "; - lastX = i.x2; - lastY = i.y2; - } - break; + intersectWithEdgeTableLine (y, tempLine); +} - case Path::Iterator::cubicTo: - writeXY (i.x1, i.y1); - writeXY (i.x2, i.y2); - writeXY (i.x3, i.y3); - out << "ct "; - lastX = i.x3; - lastY = i.y3; - break; +bool EdgeTable::isEmpty() throw() +{ + if (needToCheckEmptinesss) + { + needToCheckEmptinesss = false; + int* t = table; - case Path::Iterator::closePath: - out << "cp "; - break; + for (int i = bounds.getHeight(); --i >= 0;) + { + if (t[0] > 1) + return false; - default: - jassertfalse; - break; + t += lineStrideElements; } + + bounds.setHeight (0); } - out << '\n'; + return bounds.getHeight() == 0; } -void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const +END_JUCE_NAMESPACE +/*** End of inlined file: juce_EdgeTable.cpp ***/ + + +/*** Start of inlined file: juce_FillType.cpp ***/ +BEGIN_JUCE_NAMESPACE + +FillType::FillType() throw() + : colour (0xff000000), image (0) { - out << "[ " - << trans.mat00 << ' ' - << trans.mat10 << ' ' - << trans.mat01 << ' ' - << trans.mat11 << ' ' - << trans.mat02 << ' ' - << trans.mat12 << " ] concat "; } -void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType) +FillType::FillType (const Colour& colour_) throw() + : colour (colour_), image (0) { - stateStack.getLast()->fillType = fillType; } -void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/) +FillType::FillType (const ColourGradient& gradient_) + : colour (0xff000000), gradient (new ColourGradient (gradient_)), image (0) { } -void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) +FillType::FillType (const Image& image_, const AffineTransform& transform_) throw() + : colour (0xff000000), image (image_), transform (transform_) { } -void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle& r, const bool /*replaceExistingContents*/) +FillType::FillType (const FillType& other) + : colour (other.colour), + gradient (other.gradient != 0 ? new ColourGradient (*other.gradient) : 0), + image (other.image), transform (other.transform) { - if (stateStack.getLast()->fillType.isColour()) +} + +FillType& FillType::operator= (const FillType& other) +{ + if (this != &other) { - writeClip(); - writeColour (stateStack.getLast()->fillType.colour); + colour = other.colour; + gradient = (other.gradient != 0 ? new ColourGradient (*other.gradient) : 0); + image = other.image; + transform = other.transform; + } - Rectangle r2 (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); + return *this; +} - out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n"; +FillType::~FillType() throw() +{ +} + +bool FillType::operator== (const FillType& other) const +{ + return colour == other.colour && image == other.image + && transform == other.transform + && (gradient == other.gradient + || (gradient != 0 && other.gradient != 0 && *gradient == *other.gradient)); +} + +bool FillType::operator!= (const FillType& other) const +{ + return ! operator== (other); +} + +void FillType::setColour (const Colour& newColour) throw() +{ + gradient = 0; + image = Image(); + colour = newColour; +} + +void FillType::setGradient (const ColourGradient& newGradient) +{ + if (gradient != 0) + { + *gradient = newGradient; } else { - Path p; - p.addRectangle (r); - fillPath (p, AffineTransform::identity); + image = Image(); + gradient = new ColourGradient (newGradient); + colour = Colours::black; } +} +void FillType::setTiledImage (const Image& image_, const AffineTransform& transform_) throw() +{ + gradient = 0; + image = image_; + transform = transform_; + colour = Colours::black; } -void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t) +void FillType::setOpacity (const float newOpacity) throw() { - if (stateStack.getLast()->fillType.isColour()) - { - writeClip(); + colour = colour.withAlpha (newOpacity); +} - Path p (path); - p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, - (float) stateStack.getLast()->yOffset)); - writePath (p); +bool FillType::isInvisible() const throw() +{ + return colour.isTransparent() || (gradient != 0 && gradient->isInvisible()); +} - writeColour (stateStack.getLast()->fillType.colour); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_FillType.cpp ***/ - out << "fill\n"; - } - else if (stateStack.getLast()->fillType.isGradient()) - { - // this doesn't work correctly yet - it could be improved to handle solid gradients, but - // postscript can't do semi-transparent ones. - notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file - writeClip(); - out << "gsave "; +/*** Start of inlined file: juce_Graphics.cpp ***/ +BEGIN_JUCE_NAMESPACE - { - Path p (path); - p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); - writePath (p); - out << "clip\n"; - } +template +static bool areCoordsSensibleNumbers (Type x, Type y, Type w, Type h) +{ + const int maxVal = 0x3fffffff; - const Rectangle bounds (stateStack.getLast()->clip.getBounds()); + return (int) x >= -maxVal && (int) x <= maxVal + && (int) y >= -maxVal && (int) y <= maxVal + && (int) w >= -maxVal && (int) w <= maxVal + && (int) h >= -maxVal && (int) h <= maxVal; +} - // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the - // time-being, this just fills it with the average colour.. - writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f)); - out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; +LowLevelGraphicsContext::LowLevelGraphicsContext() +{ +} - out << "grestore\n"; - } +LowLevelGraphicsContext::~LowLevelGraphicsContext() +{ } -void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, - const int sx, const int sy, - const int maxW, const int maxH) const +Graphics::Graphics (const Image& imageToDrawOnto) + : context (imageToDrawOnto.createLowLevelContext()), + contextToDelete (context), + saveStatePending (false) { - out << "{<\n"; +} - const int w = jmin (maxW, im.getWidth()); - const int h = jmin (maxH, im.getHeight()); +Graphics::Graphics (LowLevelGraphicsContext* const internalContext) throw() + : context (internalContext), + saveStatePending (false) +{ +} - int charsOnLine = 0; - const Image::BitmapData srcData (im, 0, 0, w, h); - Colour pixel; +Graphics::~Graphics() +{ +} - for (int y = h; --y >= 0;) - { - for (int x = 0; x < w; ++x) - { - const uint8* pixelData = srcData.getPixelPointer (x, y); +void Graphics::resetToDefaultState() +{ + saveStateIfPending(); + context->setFill (FillType()); + context->setFont (Font()); + context->setInterpolationQuality (Graphics::mediumResamplingQuality); +} - if (x >= sx && y >= sy) - { - if (im.isARGB()) - { - PixelARGB p (*(const PixelARGB*) pixelData); - p.unpremultiply(); - pixel = Colours::white.overlaidWith (Colour (p.getARGB())); - } - else if (im.isRGB()) - { - pixel = Colour (((const PixelRGB*) pixelData)->getARGB()); - } - else - { - pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); - } - } - else - { - pixel = Colours::transparentWhite; - } +bool Graphics::isVectorDevice() const +{ + return context->isVectorDevice(); +} - const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() }; +bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) +{ + saveStateIfPending(); + return context->clipToRectangle (Rectangle (x, y, w, h)); +} - out << String::toHexString (pixelValues, 3, 0); - charsOnLine += 3; +bool Graphics::reduceClipRegion (const RectangleList& clipRegion) +{ + saveStateIfPending(); + return context->clipToRectangleList (clipRegion); +} - if (charsOnLine > 100) - { - out << '\n'; - charsOnLine = 0; - } - } - } +bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) +{ + saveStateIfPending(); + context->clipToPath (path, transform); + return ! context->isClipEmpty(); +} - out << "\n>}\n"; +bool Graphics::reduceClipRegion (const Image& image, const Rectangle& sourceClipRegion, const AffineTransform& transform) +{ + saveStateIfPending(); + context->clipToImageAlpha (image, sourceClipRegion, transform); + return ! context->isClipEmpty(); } -void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const Rectangle& srcClip, - const AffineTransform& transform, const bool /*fillEntireClipAsTiles*/) +void Graphics::excludeClipRegion (const Rectangle& rectangleToExclude) { - const int w = jmin (sourceImage.getWidth(), srcClip.getRight()); - const int h = jmin (sourceImage.getHeight(), srcClip.getBottom()); + saveStateIfPending(); + context->excludeClipRectangle (rectangleToExclude); +} - writeClip(); +bool Graphics::isClipEmpty() const +{ + return context->isClipEmpty(); +} - out << "gsave "; - writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) - .scaled (1.0f, -1.0f)); +const Rectangle Graphics::getClipBounds() const +{ + return context->getClipBounds(); +} - RectangleList imageClip; - sourceImage.createSolidAreaMask (imageClip, 0.5f); - imageClip.clipTo (srcClip); +void Graphics::saveState() +{ + saveStateIfPending(); + saveStatePending = true; +} - out << "newpath "; - int itemsOnLine = 0; +void Graphics::restoreState() +{ + if (saveStatePending) + saveStatePending = false; + else + context->restoreState(); +} - for (RectangleList::Iterator i (imageClip); i.next();) +void Graphics::saveStateIfPending() +{ + if (saveStatePending) { - if (++itemsOnLine == 6) - { - out << '\n'; - itemsOnLine = 0; - } - - const Rectangle& r = *i.getRectangle(); - - out << r.getX() << ' ' << r.getY() << ' ' << r.getWidth() << ' ' << r.getHeight() << " pr "; + saveStatePending = false; + context->saveState(); } - - out << " clip newpath\n"; - - out << w << ' ' << h << " scale\n"; - out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; - - writeImage (sourceImage, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); - - out << "false 3 colorimage grestore\n"; - needToClip = true; } -void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) +void Graphics::setOrigin (const int newOriginX, const int newOriginY) { - Path p; - p.addLineSegment (line, 1.0f); - fillPath (p, AffineTransform::identity); + saveStateIfPending(); + context->setOrigin (newOriginX, newOriginY); } -void LowLevelGraphicsPostScriptRenderer::drawVerticalLine (const int x, float top, float bottom) +bool Graphics::clipRegionIntersects (const Rectangle& area) const { - drawLine (Line ((float) x, top, (float) x, bottom)); + return context->clipRegionIntersects (area); } -void LowLevelGraphicsPostScriptRenderer::drawHorizontalLine (const int y, float left, float right) +void Graphics::setColour (const Colour& newColour) { - drawLine (Line (left, (float) y, right, (float) y)); + saveStateIfPending(); + context->setFill (newColour); } -void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont) +void Graphics::setOpacity (const float newOpacity) { - stateStack.getLast()->font = newFont; + saveStateIfPending(); + context->setOpacity (newOpacity); } -const Font LowLevelGraphicsPostScriptRenderer::getFont() +void Graphics::setGradientFill (const ColourGradient& gradient) { - return stateStack.getLast()->font; + setFillType (gradient); } -void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) +void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity) { - Path p; - Font& font = stateStack.getLast()->font; - font.getTypeface()->getOutlineForGlyph (glyphNumber, p); - fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); + saveStateIfPending(); + context->setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY))); + context->setOpacity (opacity); } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_LowLevelGraphicsPostScriptRenderer.cpp ***/ - - -/*** Start of inlined file: juce_LowLevelGraphicsSoftwareRenderer.cpp ***/ -BEGIN_JUCE_NAMESPACE - -#if (JUCE_WINDOWS || JUCE_LINUX) && ! JUCE_64BIT - #define JUCE_USE_SSE_INSTRUCTIONS 1 -#endif +void Graphics::setFillType (const FillType& newFill) +{ + saveStateIfPending(); + context->setFill (newFill); +} -#if JUCE_MSVC - #pragma warning (push) - #pragma warning (disable: 4127) // "expression is constant" warning +void Graphics::setFont (const Font& newFont) +{ + saveStateIfPending(); + context->setFont (newFont); +} - #if JUCE_DEBUG - #pragma optimize ("t", on) // optimise just this file, to avoid sluggish graphics when debugging - #pragma warning (disable: 4714) // warning about forcedinline methods not being inlined - #endif -#endif +void Graphics::setFont (const float newFontHeight, const int newFontStyleFlags) +{ + saveStateIfPending(); + Font f (context->getFont()); + f.setSizeAndStyle (newFontHeight, newFontStyleFlags, 1.0f, 0); + context->setFont (f); +} -namespace SoftwareRendererClasses +const Font Graphics::getCurrentFont() const { + return context->getFont(); +} -template -class SolidColourEdgeTableRenderer +void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY) const { -public: - SolidColourEdgeTableRenderer (const Image::BitmapData& data_, const PixelARGB& colour) - : data (data_), - sourceColour (colour) + if (text.isNotEmpty() + && startX < context->getClipBounds().getRight()) { - if (sizeof (PixelType) == 3) - { - areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() - && sourceColour.getGreen() == sourceColour.getBlue(); - filler[0].set (sourceColour); - filler[1].set (sourceColour); - filler[2].set (sourceColour); - filler[3].set (sourceColour); - } + GlyphArrangement arr; + arr.addLineOfText (context->getFont(), text, (float) startX, (float) baselineY); + arr.draw (*this); } +} - forcedinline void setEdgeTableYPos (const int y) throw() +void Graphics::drawTextAsPath (const String& text, const AffineTransform& transform) const +{ + if (text.isNotEmpty()) { - linePixels = (PixelType*) data.getLinePointer (y); + GlyphArrangement arr; + arr.addLineOfText (context->getFont(), text, 0.0f, 0.0f); + arr.draw (*this, transform); } +} - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() +void Graphics::drawMultiLineText (const String& text, const int startX, const int baselineY, const int maximumLineWidth) const +{ + if (text.isNotEmpty() + && startX < context->getClipBounds().getRight()) { - if (replaceExisting) - linePixels[x].set (sourceColour); - else - linePixels[x].blend (sourceColour, alphaLevel); + GlyphArrangement arr; + arr.addJustifiedText (context->getFont(), text, + (float) startX, (float) baselineY, (float) maximumLineWidth, + Justification::left); + arr.draw (*this); } +} - forcedinline void handleEdgeTablePixelFull (const int x) const throw() +void Graphics::drawText (const String& text, + const int x, const int y, const int width, const int height, + const Justification& justificationType, + const bool useEllipsesIfTooBig) const +{ + if (text.isNotEmpty() && context->clipRegionIntersects (Rectangle (x, y, width, height))) { - if (replaceExisting) - linePixels[x].set (sourceColour); - else - linePixels[x].blend (sourceColour); + GlyphArrangement arr; + + arr.addCurtailedLineOfText (context->getFont(), text, + 0.0f, 0.0f, (float) width, + useEllipsesIfTooBig); + + arr.justifyGlyphs (0, arr.getNumGlyphs(), + (float) x, (float) y, (float) width, (float) height, + justificationType); + arr.draw (*this); } +} - forcedinline void handleEdgeTableLine (const int x, const int width, const int alphaLevel) const throw() +void Graphics::drawFittedText (const String& text, + const int x, const int y, const int width, const int height, + const Justification& justification, + const int maximumNumberOfLines, + const float minimumHorizontalScale) const +{ + if (text.isNotEmpty() + && width > 0 && height > 0 + && context->clipRegionIntersects (Rectangle (x, y, width, height))) { - PixelARGB p (sourceColour); - p.multiplyAlpha (alphaLevel); + GlyphArrangement arr; - PixelType* dest = linePixels + x; + arr.addFittedText (context->getFont(), text, + (float) x, (float) y, (float) width, (float) height, + justification, + maximumNumberOfLines, + minimumHorizontalScale); - if (replaceExisting || p.getAlpha() >= 0xff) - replaceLine (dest, p, width); - else - blendLine (dest, p, width); + arr.draw (*this); } +} - forcedinline void handleEdgeTableLineFull (const int x, const int width) const throw() - { - PixelType* dest = linePixels + x; +void Graphics::fillRect (int x, int y, int width, int height) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); - if (replaceExisting || sourceColour.getAlpha() >= 0xff) - replaceLine (dest, sourceColour, width); - else - blendLine (dest, sourceColour, width); - } + context->fillRect (Rectangle (x, y, width, height), false); +} -private: - const Image::BitmapData& data; - PixelType* linePixels; - PixelARGB sourceColour; - PixelRGB filler [4]; - bool areRGBComponentsEqual; +void Graphics::fillRect (const Rectangle& r) const +{ + context->fillRect (r, false); +} - inline void blendLine (PixelType* dest, const PixelARGB& colour, int width) const throw() +void Graphics::fillRect (const float x, const float y, const float width, const float height) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); + + Path p; + p.addRectangle (x, y, width, height); + fillPath (p); +} + +void Graphics::setPixel (int x, int y) const +{ + context->fillRect (Rectangle (x, y, 1, 1), false); +} + +void Graphics::fillAll() const +{ + fillRect (context->getClipBounds()); +} + +void Graphics::fillAll (const Colour& colourToUse) const +{ + if (! colourToUse.isTransparent()) { - do - { - dest->blend (colour); - ++dest; - } while (--width > 0); + const Rectangle clip (context->getClipBounds()); + + context->saveState(); + context->setFill (colourToUse); + context->fillRect (clip, false); + context->restoreState(); } +} - forcedinline void replaceLine (PixelRGB* dest, const PixelARGB& colour, int width) const throw() - { - if (areRGBComponentsEqual) // if all the component values are the same, we can cheat.. - { - memset (dest, colour.getRed(), width * 3); - } - else - { - if (width >> 5) - { - const int* const intFiller = (const int*) filler; +void Graphics::fillPath (const Path& path, const AffineTransform& transform) const +{ + if ((! context->isClipEmpty()) && ! path.isEmpty()) + context->fillPath (path, transform); +} - while (width > 8 && (((pointer_sized_int) dest) & 7) != 0) - { - dest->set (colour); - ++dest; - --width; - } +void Graphics::strokePath (const Path& path, + const PathStrokeType& strokeType, + const AffineTransform& transform) const +{ + Path stroke; + strokeType.createStrokedPath (stroke, path, transform); + fillPath (stroke); +} - while (width > 4) - { - ((int*) dest) [0] = intFiller[0]; - ((int*) dest) [1] = intFiller[1]; - ((int*) dest) [2] = intFiller[2]; - dest = (PixelRGB*) (((uint8*) dest) + 12); - width -= 4; - } - } +void Graphics::drawRect (const int x, const int y, const int width, const int height, + const int lineThickness) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); - while (--width >= 0) - { - dest->set (colour); - ++dest; - } - } - } + context->fillRect (Rectangle (x, y, width, lineThickness), false); + context->fillRect (Rectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2), false); + context->fillRect (Rectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2), false); + context->fillRect (Rectangle (x, y + height - lineThickness, width, lineThickness), false); +} - forcedinline void replaceLine (PixelAlpha* const dest, const PixelARGB& colour, int const width) const throw() - { - memset (dest, colour.getAlpha(), width); - } +void Graphics::drawRect (const float x, const float y, const float width, const float height, const float lineThickness) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); - forcedinline void replaceLine (PixelARGB* dest, const PixelARGB& colour, int width) const throw() + Path p; + p.addRectangle (x, y, width, lineThickness); + p.addRectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2.0f); + p.addRectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2.0f); + p.addRectangle (x, y + height - lineThickness, width, lineThickness); + fillPath (p); +} + +void Graphics::drawRect (const Rectangle& r, const int lineThickness) const +{ + drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), lineThickness); +} + +void Graphics::drawBevel (const int x, const int y, const int width, const int height, + const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour, + const bool useGradient, const bool sharpEdgeOnOutside) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); + + if (clipRegionIntersects (Rectangle (x, y, width, height))) { - do + context->saveState(); + + const float oldOpacity = 1.0f;//xxx state->colour.getFloatAlpha(); + const float ramp = oldOpacity / bevelThickness; + + for (int i = bevelThickness; --i >= 0;) { - dest->set (colour); - ++dest; + const float op = useGradient ? ramp * (sharpEdgeOnOutside ? bevelThickness - i : i) + : oldOpacity; - } while (--width > 0); + context->setFill (topLeftColour.withMultipliedAlpha (op)); + context->fillRect (Rectangle (x + i, y + i, width - i * 2, 1), false); + context->setFill (topLeftColour.withMultipliedAlpha (op * 0.75f)); + context->fillRect (Rectangle (x + i, y + i + 1, 1, height - i * 2 - 2), false); + context->setFill (bottomRightColour.withMultipliedAlpha (op)); + context->fillRect (Rectangle (x + i, y + height - i - 1, width - i * 2, 1), false); + context->setFill (bottomRightColour.withMultipliedAlpha (op * 0.75f)); + context->fillRect (Rectangle (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2), false); + } + + context->restoreState(); } +} - SolidColourEdgeTableRenderer (const SolidColourEdgeTableRenderer&); - SolidColourEdgeTableRenderer& operator= (const SolidColourEdgeTableRenderer&); -}; +void Graphics::fillEllipse (const float x, const float y, const float width, const float height) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); -class LinearGradientPixelGenerator + Path p; + p.addEllipse (x, y, width, height); + fillPath (p); +} + +void Graphics::drawEllipse (const float x, const float y, const float width, const float height, + const float lineThickness) const { -public: - LinearGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, const PixelARGB* const lookupTable_, const int numEntries_) - : lookupTable (lookupTable_), numEntries (numEntries_) - { - jassert (numEntries_ >= 0); - Point p1 (gradient.point1); - Point p2 (gradient.point2); + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); - if (! transform.isIdentity()) - { - const Line l (p2, p1); - Point p3 = l.getPointAlongLine (0.0f, 100.0f); + Path p; + p.addEllipse (x, y, width, height); + strokePath (p, PathStrokeType (lineThickness)); +} - p1.applyTransform (transform); - p2.applyTransform (transform); - p3.applyTransform (transform); +void Graphics::fillRoundedRectangle (const float x, const float y, const float width, const float height, const float cornerSize) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); - p2 = Line (p2, p3).findNearestPointTo (p1); - } + Path p; + p.addRoundedRectangle (x, y, width, height, cornerSize); + fillPath (p); +} - vertical = std::abs (p1.getX() - p2.getX()) < 0.001f; - horizontal = std::abs (p1.getY() - p2.getY()) < 0.001f; +void Graphics::fillRoundedRectangle (const Rectangle& r, const float cornerSize) const +{ + fillRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize); +} - if (vertical) - { - scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.getY() - p1.getY())); - start = roundToInt (p1.getY() * scale); - } - else if (horizontal) +void Graphics::drawRoundedRectangle (const float x, const float y, const float width, const float height, + const float cornerSize, const float lineThickness) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (x, y, width, height)); + + Path p; + p.addRoundedRectangle (x, y, width, height, cornerSize); + strokePath (p, PathStrokeType (lineThickness)); +} + +void Graphics::drawRoundedRectangle (const Rectangle& r, const float cornerSize, const float lineThickness) const +{ + drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness); +} + +void Graphics::drawArrow (const Line& line, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const +{ + Path p; + p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); + fillPath (p); +} + +void Graphics::fillCheckerBoard (const Rectangle& area, + const int checkWidth, const int checkHeight, + const Colour& colour1, const Colour& colour2) const +{ + jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! + + if (checkWidth > 0 && checkHeight > 0) + { + context->saveState(); + + if (colour1 == colour2) { - scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.getX() - p1.getX())); - start = roundToInt (p1.getX() * scale); + context->setFill (colour1); + context->fillRect (area, false); } else { - grad = (p2.getY() - p1.getY()) / (double) (p1.getX() - p2.getX()); - yTerm = p1.getY() - p1.getX() / grad; - scale = roundToInt ((numEntries << (int) numScaleBits) / (yTerm * grad - (p2.getY() * grad - p2.getX()))); - grad *= scale; + const Rectangle clipped (context->getClipBounds().getIntersection (area)); + + if (! clipped.isEmpty()) + { + context->clipToRectangle (clipped); + + const int checkNumX = (clipped.getX() - area.getX()) / checkWidth; + const int checkNumY = (clipped.getY() - area.getY()) / checkHeight; + const int startX = area.getX() + checkNumX * checkWidth; + const int startY = area.getY() + checkNumY * checkHeight; + const int right = clipped.getRight(); + const int bottom = clipped.getBottom(); + + for (int i = 0; i < 2; ++i) + { + context->setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2); + + int cy = i; + for (int y = startY; y < bottom; y += checkHeight) + for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2) + context->fillRect (Rectangle (x, y, checkWidth, checkHeight), false); + } + } } - } - forcedinline void setY (const int y) throw() - { - if (vertical) - linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> (int) numScaleBits)]; - else if (! horizontal) - start = roundToInt ((y - yTerm) * grad); + context->restoreState(); } +} - inline const PixelARGB getPixel (const int x) const throw() - { - return vertical ? linePix - : lookupTable [jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; - } +void Graphics::drawVerticalLine (const int x, float top, float bottom) const +{ + context->drawVerticalLine (x, top, bottom); +} -private: - const PixelARGB* const lookupTable; - const int numEntries; - PixelARGB linePix; - int start, scale; - double grad, yTerm; - bool vertical, horizontal; - enum { numScaleBits = 12 }; +void Graphics::drawHorizontalLine (const int y, float left, float right) const +{ + context->drawHorizontalLine (y, left, right); +} - LinearGradientPixelGenerator (const LinearGradientPixelGenerator&); - LinearGradientPixelGenerator& operator= (const LinearGradientPixelGenerator&); -}; +void Graphics::drawLine (float x1, float y1, float x2, float y2) const +{ + context->drawLine (Line (x1, y1, x2, y2)); +} -class RadialGradientPixelGenerator +void Graphics::drawLine (const float startX, const float startY, + const float endX, const float endY, + const float lineThickness) const { -public: - RadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform&, - const PixelARGB* const lookupTable_, const int numEntries_) - : lookupTable (lookupTable_), - numEntries (numEntries_), - gx1 (gradient.point1.getX()), - gy1 (gradient.point1.getY()) - { - jassert (numEntries_ >= 0); - const Point diff (gradient.point1 - gradient.point2); - maxDist = diff.getX() * diff.getX() + diff.getY() * diff.getY(); - invScale = numEntries / std::sqrt (maxDist); - jassert (roundToInt (std::sqrt (maxDist) * invScale) <= numEntries); - } + drawLine (Line (startX, startY, endX, endY),lineThickness); +} - forcedinline void setY (const int y) throw() - { - dy = y - gy1; - dy *= dy; - } +void Graphics::drawLine (const Line& line) const +{ + drawLine (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY()); +} - inline const PixelARGB getPixel (const int px) const throw() - { - double x = px - gx1; - x *= x; - x += dy; +void Graphics::drawLine (const Line& line, const float lineThickness) const +{ + Path p; + p.addLineSegment (line, lineThickness); + fillPath (p); +} - return lookupTable [x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; - } +void Graphics::drawDashedLine (const float startX, const float startY, + const float endX, const float endY, + const float* const dashLengths, + const int numDashLengths, + const float lineThickness) const +{ + const double dx = endX - startX; + const double dy = endY - startY; + const double totalLen = juce_hypot (dx, dy); -protected: - const PixelARGB* const lookupTable; - const int numEntries; - const double gx1, gy1; - double maxDist, invScale, dy; + if (totalLen >= 0.5) + { + const double onePixAlpha = 1.0 / totalLen; - RadialGradientPixelGenerator (const RadialGradientPixelGenerator&); - RadialGradientPixelGenerator& operator= (const RadialGradientPixelGenerator&); -}; + double alpha = 0.0; + float x = startX; + float y = startY; + int n = 0; -class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator -{ -public: - TransformedRadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, - const PixelARGB* const lookupTable_, const int numEntries_) - : RadialGradientPixelGenerator (gradient, transform, lookupTable_, numEntries_), - inverseTransform (transform.inverted()) - { - tM10 = inverseTransform.mat10; - tM00 = inverseTransform.mat00; - } + while (alpha < 1.0f) + { + alpha = jmin (1.0, alpha + dashLengths[n++] * onePixAlpha); + n = n % numDashLengths; - forcedinline void setY (const int y) throw() - { - lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; - lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; - } + const float oldX = x; + const float oldY = y; - inline const PixelARGB getPixel (const int px) const throw() - { - double x = px; - const double y = tM10 * x + lineYM11; - x = tM00 * x + lineYM01; - x *= x; - x += y * y; + x = (float) (startX + dx * alpha); + y = (float) (startY + dy * alpha); - if (x >= maxDist) - return lookupTable [numEntries]; - else - return lookupTable [jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; + if ((n & 1) != 0) + { + if (lineThickness != 1.0f) + drawLine (oldX, oldY, x, y, lineThickness); + else + drawLine (oldX, oldY, x, y); + } + } } +} -private: - double tM10, tM00, lineYM01, lineYM11; - const AffineTransform inverseTransform; +void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) +{ + saveStateIfPending(); + context->setInterpolationQuality (newQuality); +} - TransformedRadialGradientPixelGenerator (const TransformedRadialGradientPixelGenerator&); - TransformedRadialGradientPixelGenerator& operator= (const TransformedRadialGradientPixelGenerator&); -}; +void Graphics::drawImageAt (const Image& imageToDraw, + const int topLeftX, const int topLeftY, + const bool fillAlphaChannelWithCurrentBrush) const +{ + const int imageW = imageToDraw.getWidth(); + const int imageH = imageToDraw.getHeight(); -template -class GradientEdgeTableRenderer : public GradientType + drawImage (imageToDraw, + topLeftX, topLeftY, imageW, imageH, + 0, 0, imageW, imageH, + fillAlphaChannelWithCurrentBrush); +} + +void Graphics::drawImageWithin (const Image& imageToDraw, + const int destX, const int destY, + const int destW, const int destH, + const RectanglePlacement& placementWithinTarget, + const bool fillAlphaChannelWithCurrentBrush) const { -public: - GradientEdgeTableRenderer (const Image::BitmapData& destData_, const ColourGradient& gradient, const AffineTransform& transform, - const PixelARGB* const lookupTable_, const int numEntries_) - : GradientType (gradient, transform, lookupTable_, numEntries_ - 1), - destData (destData_) - { - } + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (destX, destY, destW, destH)); - forcedinline void setEdgeTableYPos (const int y) throw() + if (imageToDraw.isValid()) { - linePixels = (PixelType*) destData.getLinePointer (y); - GradientType::setY (y); - } + const int imageW = imageToDraw.getWidth(); + const int imageH = imageToDraw.getHeight(); - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() - { - linePixels[x].blend (GradientType::getPixel (x), alphaLevel); + if (imageW > 0 && imageH > 0) + { + double newX = 0.0, newY = 0.0; + double newW = imageW; + double newH = imageH; + + placementWithinTarget.applyTo (newX, newY, newW, newH, + destX, destY, destW, destH); + + if (newW > 0 && newH > 0) + { + drawImage (imageToDraw, + roundToInt (newX), roundToInt (newY), + roundToInt (newW), roundToInt (newH), + 0, 0, imageW, imageH, + fillAlphaChannelWithCurrentBrush); + } + } } +} - forcedinline void handleEdgeTablePixelFull (const int x) const throw() +void Graphics::drawImage (const Image& imageToDraw, + int dx, int dy, int dw, int dh, + int sx, int sy, int sw, int sh, + const bool fillAlphaChannelWithCurrentBrush) const +{ + // passing in a silly number can cause maths problems in rendering! + jassert (areCoordsSensibleNumbers (dx, dy, dw, dh)); + jassert (areCoordsSensibleNumbers (sx, sy, sw, sh)); + + if (imageToDraw.isValid() && context->clipRegionIntersects (Rectangle (dx, dy, dw, dh))) { - linePixels[x].blend (GradientType::getPixel (x)); + drawImageTransformed (imageToDraw, Rectangle (sx, sy, sw, sh), + AffineTransform::scale (dw / (float) sw, dh / (float) sh) + .translated ((float) dx, (float) dy), + fillAlphaChannelWithCurrentBrush); } +} - void handleEdgeTableLine (int x, int width, const int alphaLevel) const throw() +void Graphics::drawImageTransformed (const Image& imageToDraw, + const Rectangle& imageSubRegion, + const AffineTransform& transform, + const bool fillAlphaChannelWithCurrentBrush) const +{ + if (imageToDraw.isValid() && ! context->isClipEmpty()) { - PixelType* dest = linePixels + x; + const Rectangle srcClip (imageSubRegion.getIntersection (imageToDraw.getBounds())); - if (alphaLevel < 0xff) + if (fillAlphaChannelWithCurrentBrush) { - do - { - (dest++)->blend (GradientType::getPixel (x++), alphaLevel); - } while (--width > 0); + context->saveState(); + context->clipToImageAlpha (imageToDraw, srcClip, transform); + fillAll(); + context->restoreState(); } else { - do - { - (dest++)->blend (GradientType::getPixel (x++)); - } while (--width > 0); + context->drawImage (imageToDraw, srcClip, transform, false); } } +} - void handleEdgeTableLineFull (int x, int width) const throw() - { - PixelType* dest = linePixels + x; - - do - { - (dest++)->blend (GradientType::getPixel (x++)); - } while (--width > 0); - } +END_JUCE_NAMESPACE +/*** End of inlined file: juce_Graphics.cpp ***/ -private: - const Image::BitmapData& destData; - PixelType* linePixels; - GradientEdgeTableRenderer (const GradientEdgeTableRenderer&); - GradientEdgeTableRenderer& operator= (const GradientEdgeTableRenderer&); -}; +/*** Start of inlined file: juce_Justification.cpp ***/ +BEGIN_JUCE_NAMESPACE -static forcedinline int safeModulo (int n, const int divisor) throw() +Justification::Justification (const Justification& other) throw() + : flags (other.flags) { - jassert (divisor > 0); - n %= divisor; - return (n < 0) ? (n + divisor) : n; } -template -class ImageFillEdgeTableRenderer +Justification& Justification::operator= (const Justification& other) throw() { -public: - ImageFillEdgeTableRenderer (const Image::BitmapData& destData_, - const Image::BitmapData& srcData_, - const int extraAlpha_, - const int x, const int y) - : destData (destData_), - srcData (srcData_), - extraAlpha (extraAlpha_ + 1), - xOffset (repeatPattern ? safeModulo (x, srcData_.width) - srcData_.width : x), - yOffset (repeatPattern ? safeModulo (y, srcData_.height) - srcData_.height : y) - { - } + flags = other.flags; + return *this; +} - forcedinline void setEdgeTableYPos (int y) throw() - { - linePixels = (DestPixelType*) destData.getLinePointer (y); +int Justification::getOnlyVerticalFlags() const throw() +{ + return flags & (top | bottom | verticallyCentred); +} - y -= yOffset; - if (repeatPattern) - { - jassert (y >= 0); - y %= srcData.height; - } +int Justification::getOnlyHorizontalFlags() const throw() +{ + return flags & (left | right | horizontallyCentred | horizontallyJustified); +} - sourceLineStart = (SrcPixelType*) srcData.getLinePointer (y); - } +void Justification::applyToRectangle (int& x, int& y, + const int w, const int h, + const int spaceX, const int spaceY, + const int spaceW, const int spaceH) const throw() +{ + if ((flags & horizontallyCentred) != 0) + x = spaceX + ((spaceW - w) >> 1); + else if ((flags & right) != 0) + x = spaceX + spaceW - w; + else + x = spaceX; - forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const throw() - { - alphaLevel = (alphaLevel * extraAlpha) >> 8; + if ((flags & verticallyCentred) != 0) + y = spaceY + ((spaceH - h) >> 1); + else if ((flags & bottom) != 0) + y = spaceY + spaceH - h; + else + y = spaceY; +} - linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], alphaLevel); - } +END_JUCE_NAMESPACE +/*** End of inlined file: juce_Justification.cpp ***/ - forcedinline void handleEdgeTablePixelFull (const int x) const throw() - { - linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], extraAlpha); - } - void handleEdgeTableLine (int x, int width, int alphaLevel) const throw() - { - DestPixelType* dest = linePixels + x; - alphaLevel = (alphaLevel * extraAlpha) >> 8; - x -= xOffset; +/*** Start of inlined file: juce_LowLevelGraphicsPostScriptRenderer.cpp ***/ +BEGIN_JUCE_NAMESPACE - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); +// this will throw an assertion if you try to draw something that's not +// possible in postscript +#define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 - if (alphaLevel < 0xfe) - { - do - { - dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], alphaLevel); - } while (--width > 0); - } - else - { - if (repeatPattern) - { - do - { - dest++ ->blend (sourceLineStart [x++ % srcData.width]); - } while (--width > 0); - } - else - { - copyRow (dest, sourceLineStart + x, width); - } - } - } +#if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS + #define notPossibleInPostscriptAssert jassertfalse +#else + #define notPossibleInPostscriptAssert +#endif - void handleEdgeTableLineFull (int x, int width) const throw() - { - DestPixelType* dest = linePixels + x; - x -= xOffset; +LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, + const String& documentTitle, + const int totalWidth_, + const int totalHeight_) + : out (resultingPostScript), + totalWidth (totalWidth_), + totalHeight (totalHeight_), + needToClip (true) +{ + stateStack.add (new SavedState()); + stateStack.getLast()->clip = Rectangle (totalWidth_, totalHeight_); - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); + const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); - if (extraAlpha < 0xfe) - { - do - { - dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], extraAlpha); - } while (--width > 0); - } - else - { - if (repeatPattern) - { - do - { - dest++ ->blend (sourceLineStart [x++ % srcData.width]); - } while (--width > 0); - } - else - { - copyRow (dest, sourceLineStart + x, width); - } - } - } + out << "%!PS-Adobe-3.0 EPSF-3.0" + "\n%%BoundingBox: 0 0 600 824" + "\n%%Pages: 0" + "\n%%Creator: Raw Material Software JUCE" + "\n%%Title: " << documentTitle << + "\n%%CreationDate: none" + "\n%%LanguageLevel: 2" + "\n%%EndComments" + "\n%%BeginProlog" + "\n%%BeginResource: JRes" + "\n/bd {bind def} bind def" + "\n/c {setrgbcolor} bd" + "\n/m {moveto} bd" + "\n/l {lineto} bd" + "\n/rl {rlineto} bd" + "\n/ct {curveto} bd" + "\n/cp {closepath} bd" + "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" + "\n/doclip {initclip newpath} bd" + "\n/endclip {clip newpath} bd" + "\n%%EndResource" + "\n%%EndProlog" + "\n%%BeginSetup" + "\n%%EndSetup" + "\n%%Page: 1 1" + "\n%%BeginPageSetup" + "\n%%EndPageSetup\n\n" + << "40 800 translate\n" + << scale << ' ' << scale << " scale\n\n"; +} - void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) - { - jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); - SrcPixelType* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); - uint8* mask = (uint8*) (s + x - xOffset); +LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() +{ +} - if (sizeof (SrcPixelType) == sizeof (PixelARGB)) - mask += PixelARGB::indexA; +bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const +{ + return true; +} - et.clipLineToMask (x, y, mask, sizeof (SrcPixelType), width); +void LowLevelGraphicsPostScriptRenderer::setOrigin (int x, int y) +{ + if (x != 0 || y != 0) + { + stateStack.getLast()->xOffset += x; + stateStack.getLast()->yOffset += y; + needToClip = true; } +} -private: - const Image::BitmapData& destData; - const Image::BitmapData& srcData; - const int extraAlpha, xOffset, yOffset; - DestPixelType* linePixels; - SrcPixelType* sourceLineStart; +bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle& r) +{ + needToClip = true; + return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} - template - forcedinline static void copyRow (PixelType1* dest, PixelType2* src, int width) throw() - { - do - { - dest++ ->blend (*src++); - } while (--width > 0); - } +bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList& clipRegion) +{ + needToClip = true; + return stateStack.getLast()->clip.clipTo (clipRegion); +} + +void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle& r) +{ + needToClip = true; + stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} - forcedinline static void copyRow (PixelRGB* dest, PixelRGB* src, int width) throw() - { - memcpy (dest, src, width * sizeof (PixelRGB)); - } +void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform) +{ + writeClip(); - ImageFillEdgeTableRenderer (const ImageFillEdgeTableRenderer&); - ImageFillEdgeTableRenderer& operator= (const ImageFillEdgeTableRenderer&); -}; + Path p (path); + p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); + writePath (p); + out << "clip\n"; +} -template -class TransformedImageFillEdgeTableRenderer +void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const Rectangle& /*srcClip*/, const AffineTransform& /*transform*/) { -public: - TransformedImageFillEdgeTableRenderer (const Image::BitmapData& destData_, - const Image::BitmapData& srcData_, - const AffineTransform& transform, - const int extraAlpha_, - const bool betterQuality_) - : interpolator (transform), - destData (destData_), - srcData (srcData_), - extraAlpha (extraAlpha_ + 1), - betterQuality (betterQuality_), - pixelOffset (betterQuality_ ? 0.5f : 0.0f), - pixelOffsetInt (betterQuality_ ? -128 : 0), - maxX (srcData_.width - 1), - maxY (srcData_.height - 1), - scratchSize (2048) - { - scratchBuffer.malloc (scratchSize); - } + needToClip = true; + jassertfalse; // xxx +} - ~TransformedImageFillEdgeTableRenderer() - { - } +bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle& r) +{ + return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} - forcedinline void setEdgeTableYPos (const int newY) throw() - { - y = newY; - linePixels = (DestPixelType*) destData.getLinePointer (newY); - } +const Rectangle LowLevelGraphicsPostScriptRenderer::getClipBounds() const +{ + return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset, + -stateStack.getLast()->yOffset); +} - forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) throw() - { - alphaLevel *= extraAlpha; - alphaLevel >>= 8; +bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const +{ + return stateStack.getLast()->clip.isEmpty(); +} - SrcPixelType p; - generate (&p, x, 1); +LowLevelGraphicsPostScriptRenderer::SavedState::SavedState() + : xOffset (0), + yOffset (0) +{ +} - linePixels[x].blend (p, alphaLevel); - } +LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() +{ +} - forcedinline void handleEdgeTablePixelFull (const int x) throw() - { - SrcPixelType p; - generate (&p, x, 1); +void LowLevelGraphicsPostScriptRenderer::saveState() +{ + stateStack.add (new SavedState (*stateStack.getLast())); +} - linePixels[x].blend (p, extraAlpha); - } +void LowLevelGraphicsPostScriptRenderer::restoreState() +{ + jassert (stateStack.size() > 0); - void handleEdgeTableLine (const int x, int width, int alphaLevel) throw() + if (stateStack.size() > 0) + stateStack.removeLast(); +} + +void LowLevelGraphicsPostScriptRenderer::writeClip() +{ + if (needToClip) { - if (width > scratchSize) - { - scratchSize = width; - scratchBuffer.malloc (scratchSize); - } + needToClip = false; - SrcPixelType* span = scratchBuffer; - generate (span, x, width); + out << "doclip "; - DestPixelType* dest = linePixels + x; - alphaLevel *= extraAlpha; - alphaLevel >>= 8; + int itemsOnLine = 0; - if (alphaLevel < 0xfe) - { - do - { - dest++ ->blend (*span++, alphaLevel); - } while (--width > 0); - } - else + for (RectangleList::Iterator i (stateStack.getLast()->clip); i.next();) { - do + if (++itemsOnLine == 6) { - dest++ ->blend (*span++); - } while (--width > 0); + itemsOnLine = 0; + out << '\n'; + } + + const Rectangle& r = *i.getRectangle(); + + out << r.getX() << ' ' << -r.getY() << ' ' + << r.getWidth() << ' ' << -r.getHeight() << " pr "; } + + out << "endclip\n"; } +} - forcedinline void handleEdgeTableLineFull (const int x, int width) throw() +void LowLevelGraphicsPostScriptRenderer::writeColour (const Colour& colour) +{ + Colour c (Colours::white.overlaidWith (colour)); + + if (lastColour != c) { - handleEdgeTableLine (x, width, 255); + lastColour = c; + + out << String (c.getFloatRed(), 3) << ' ' + << String (c.getFloatGreen(), 3) << ' ' + << String (c.getFloatBlue(), 3) << " c\n"; } +} - void clipEdgeTableLine (EdgeTable& et, int x, int y_, int width) - { - if (width > scratchSize) - { - scratchSize = width; - scratchBuffer.malloc (scratchSize); - } +void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const +{ + out << String (x, 2) << ' ' + << String (-y, 2) << ' '; +} - y = y_; - generate (scratchBuffer, x, width); +void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const +{ + out << "newpath "; - et.clipLineToMask (x, y_, - reinterpret_cast (scratchBuffer.getData()) + SrcPixelType::indexA, - sizeof (SrcPixelType), width); - } + float lastX = 0.0f; + float lastY = 0.0f; + int itemsOnLine = 0; -private: + Path::Iterator i (path); - void generate (PixelARGB* dest, const int x, int numPixels) throw() + while (i.next()) { - this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); + if (++itemsOnLine == 4) + { + itemsOnLine = 0; + out << '\n'; + } - do + switch (i.elementType) { - int hiResX, hiResY; - this->interpolator.next (hiResX, hiResY); - hiResX += pixelOffsetInt; - hiResY += pixelOffsetInt; + case Path::Iterator::startNewSubPath: + writeXY (i.x1, i.y1); + lastX = i.x1; + lastY = i.y1; + out << "m "; + break; - int loResX = hiResX >> 8; - int loResY = hiResY >> 8; + case Path::Iterator::lineTo: + writeXY (i.x1, i.y1); + lastX = i.x1; + lastY = i.y1; + out << "l "; + break; - if (repeatPattern) + case Path::Iterator::quadraticTo: { - loResX = safeModulo (loResX, srcData.width); - loResY = safeModulo (loResY, srcData.height); + const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; + const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; + const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; + const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; + + writeXY (cp1x, cp1y); + writeXY (cp2x, cp2y); + writeXY (i.x2, i.y2); + out << "ct "; + lastX = i.x2; + lastY = i.y2; } + break; - if (betterQuality - && ((unsigned int) loResX) < (unsigned int) maxX - && ((unsigned int) loResY) < (unsigned int) maxY) - { - uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; - hiResX &= 255; - hiResY &= 255; + case Path::Iterator::cubicTo: + writeXY (i.x1, i.y1); + writeXY (i.x2, i.y2); + writeXY (i.x3, i.y3); + out << "ct "; + lastX = i.x3; + lastY = i.y3; + break; - const uint8* src = this->srcData.getPixelPointer (loResX, loResY); + case Path::Iterator::closePath: + out << "cp "; + break; - uint32 weight = (256 - hiResX) * (256 - hiResY); - c[0] += weight * src[0]; - c[1] += weight * src[1]; - c[2] += weight * src[2]; - c[3] += weight * src[3]; + default: + jassertfalse; + break; + } + } - weight = hiResX * (256 - hiResY); - c[0] += weight * src[4]; - c[1] += weight * src[5]; - c[2] += weight * src[6]; - c[3] += weight * src[7]; + out << '\n'; +} - src += this->srcData.lineStride; +void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const +{ + out << "[ " + << trans.mat00 << ' ' + << trans.mat10 << ' ' + << trans.mat01 << ' ' + << trans.mat11 << ' ' + << trans.mat02 << ' ' + << trans.mat12 << " ] concat "; +} - weight = (256 - hiResX) * hiResY; - c[0] += weight * src[0]; - c[1] += weight * src[1]; - c[2] += weight * src[2]; - c[3] += weight * src[3]; +void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType) +{ + stateStack.getLast()->fillType = fillType; +} - weight = hiResX * hiResY; - c[0] += weight * src[4]; - c[1] += weight * src[5]; - c[2] += weight * src[6]; - c[3] += weight * src[7]; +void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/) +{ +} - dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 16), - (uint8) (c[PixelARGB::indexR] >> 16), - (uint8) (c[PixelARGB::indexG] >> 16), - (uint8) (c[PixelARGB::indexB] >> 16)); - } - else - { - if (! repeatPattern) - { - // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable - if (loResX < 0) loResX = 0; - if (loResY < 0) loResY = 0; - if (loResX > maxX) loResX = maxX; - if (loResY > maxY) loResY = maxY; - } +void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) +{ +} - dest->set (*(const PixelARGB*) this->srcData.getPixelPointer (loResX, loResY)); - } +void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle& r, const bool /*replaceExistingContents*/) +{ + if (stateStack.getLast()->fillType.isColour()) + { + writeClip(); + writeColour (stateStack.getLast()->fillType.colour); - ++dest; + Rectangle r2 (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); - } while (--numPixels > 0); + out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n"; } - - void generate (PixelRGB* dest, const int x, int numPixels) throw() + else { - this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); + Path p; + p.addRectangle (r); + fillPath (p, AffineTransform::identity); + } - do - { - int hiResX, hiResY; - this->interpolator.next (hiResX, hiResY); - hiResX += pixelOffsetInt; - hiResY += pixelOffsetInt; - int loResX = hiResX >> 8; - int loResY = hiResY >> 8; +} - if (repeatPattern) - { - loResX = safeModulo (loResX, srcData.width); - loResY = safeModulo (loResY, srcData.height); - } +void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t) +{ + if (stateStack.getLast()->fillType.isColour()) + { + writeClip(); - if (betterQuality - && ((unsigned int) loResX) < (unsigned int) maxX - && ((unsigned int) loResY) < (unsigned int) maxY) - { - uint32 c[3] = { 256 * 128, 256 * 128, 256 * 128 }; - hiResX &= 255; - hiResY &= 255; + Path p (path); + p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, + (float) stateStack.getLast()->yOffset)); + writePath (p); - const uint8* src = this->srcData.getPixelPointer (loResX, loResY); + writeColour (stateStack.getLast()->fillType.colour); - unsigned int weight = (256 - hiResX) * (256 - hiResY); - c[0] += weight * src[0]; - c[1] += weight * src[1]; - c[2] += weight * src[2]; + out << "fill\n"; + } + else if (stateStack.getLast()->fillType.isGradient()) + { + // this doesn't work correctly yet - it could be improved to handle solid gradients, but + // postscript can't do semi-transparent ones. + notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file - weight = hiResX * (256 - hiResY); - c[0] += weight * src[3]; - c[1] += weight * src[4]; - c[2] += weight * src[5]; + writeClip(); + out << "gsave "; - src += this->srcData.lineStride; + { + Path p (path); + p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); + writePath (p); + out << "clip\n"; + } - weight = (256 - hiResX) * hiResY; - c[0] += weight * src[0]; - c[1] += weight * src[1]; - c[2] += weight * src[2]; + const Rectangle bounds (stateStack.getLast()->clip.getBounds()); - weight = hiResX * hiResY; - c[0] += weight * src[3]; - c[1] += weight * src[4]; - c[2] += weight * src[5]; + // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the + // time-being, this just fills it with the average colour.. + writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f)); + out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; - dest->setARGB ((uint8) 255, - (uint8) (c[PixelRGB::indexR] >> 16), - (uint8) (c[PixelRGB::indexG] >> 16), - (uint8) (c[PixelRGB::indexB] >> 16)); - } - else - { - if (! repeatPattern) - { - // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable - if (loResX < 0) loResX = 0; - if (loResY < 0) loResY = 0; - if (loResX > maxX) loResX = maxX; - if (loResY > maxY) loResY = maxY; - } + out << "grestore\n"; + } +} - dest->set (*(const PixelRGB*) this->srcData.getPixelPointer (loResX, loResY)); - } +void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, + const int sx, const int sy, + const int maxW, const int maxH) const +{ + out << "{<\n"; - ++dest; + const int w = jmin (maxW, im.getWidth()); + const int h = jmin (maxH, im.getHeight()); - } while (--numPixels > 0); - } + int charsOnLine = 0; + const Image::BitmapData srcData (im, 0, 0, w, h); + Colour pixel; - void generate (PixelAlpha* dest, const int x, int numPixels) throw() + for (int y = h; --y >= 0;) { - this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); - - do + for (int x = 0; x < w; ++x) { - int hiResX, hiResY; - this->interpolator.next (hiResX, hiResY); - hiResX += pixelOffsetInt; - hiResY += pixelOffsetInt; - int loResX = hiResX >> 8; - int loResY = hiResY >> 8; - - if (repeatPattern) - { - loResX = safeModulo (loResX, srcData.width); - loResY = safeModulo (loResY, srcData.height); - } + const uint8* pixelData = srcData.getPixelPointer (x, y); - if (betterQuality - && ((unsigned int) loResX) < (unsigned int) maxX - && ((unsigned int) loResY) < (unsigned int) maxY) + if (x >= sx && y >= sy) { - hiResX &= 255; - hiResY &= 255; - - const uint8* src = this->srcData.getPixelPointer (loResX, loResY); - uint32 c = 256 * 128; - c += src[0] * ((256 - hiResX) * (256 - hiResY)); - c += src[1] * (hiResX * (256 - hiResY)); - src += this->srcData.lineStride; - c += src[0] * ((256 - hiResX) * hiResY); - c += src[1] * (hiResX * hiResY); - - *((uint8*) dest) = (uint8) c; + if (im.isARGB()) + { + PixelARGB p (*(const PixelARGB*) pixelData); + p.unpremultiply(); + pixel = Colours::white.overlaidWith (Colour (p.getARGB())); + } + else if (im.isRGB()) + { + pixel = Colour (((const PixelRGB*) pixelData)->getARGB()); + } + else + { + pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); + } } else { - if (! repeatPattern) - { - // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable - if (loResX < 0) loResX = 0; - if (loResY < 0) loResY = 0; - if (loResX > maxX) loResX = maxX; - if (loResY > maxY) loResY = maxY; - } - - *((uint8*) dest) = *(this->srcData.getPixelPointer (loResX, loResY)); + pixel = Colours::transparentWhite; } - ++dest; - - } while (--numPixels > 0); - } - - class TransformedImageSpanInterpolator - { - public: - TransformedImageSpanInterpolator (const AffineTransform& transform) throw() - : inverseTransform (transform.inverted()) - {} + const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() }; - void setStartOfLine (float x, float y, const int numPixels) throw() - { - float x1 = x, y1 = y; - x += numPixels; - inverseTransform.transformPoints (x1, y1, x, y); + out << String::toHexString (pixelValues, 3, 0); + charsOnLine += 3; - xBresenham.set ((int) (x1 * 256.0f), (int) (x * 256.0f), numPixels); - yBresenham.set ((int) (y1 * 256.0f), (int) (y * 256.0f), numPixels); + if (charsOnLine > 100) + { + out << '\n'; + charsOnLine = 0; + } } + } - void next (int& x, int& y) throw() - { - x = xBresenham.n; - xBresenham.stepToNext(); - y = yBresenham.n; - yBresenham.stepToNext(); - } + out << "\n>}\n"; +} - private: - class BresenhamInterpolator - { - public: - BresenhamInterpolator() throw() {} +void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const Rectangle& srcClip, + const AffineTransform& transform, const bool /*fillEntireClipAsTiles*/) +{ + const int w = jmin (sourceImage.getWidth(), srcClip.getRight()); + const int h = jmin (sourceImage.getHeight(), srcClip.getBottom()); - void set (const int n1, const int n2, const int numSteps_) throw() - { - numSteps = jmax (1, numSteps_); - step = (n2 - n1) / numSteps; - remainder = modulo = (n2 - n1) % numSteps; - n = n1; + writeClip(); - if (modulo <= 0) - { - modulo += numSteps; - remainder += numSteps; - --step; - } + out << "gsave "; + writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) + .scaled (1.0f, -1.0f)); - modulo -= numSteps; - } + RectangleList imageClip; + sourceImage.createSolidAreaMask (imageClip, 0.5f); + imageClip.clipTo (srcClip); - forcedinline void stepToNext() throw() - { - modulo += remainder; - n += step; + out << "newpath "; + int itemsOnLine = 0; - if (modulo > 0) - { - modulo -= numSteps; - ++n; - } - } + for (RectangleList::Iterator i (imageClip); i.next();) + { + if (++itemsOnLine == 6) + { + out << '\n'; + itemsOnLine = 0; + } - int n; + const Rectangle& r = *i.getRectangle(); - private: - int numSteps, step, modulo, remainder; - }; + out << r.getX() << ' ' << r.getY() << ' ' << r.getWidth() << ' ' << r.getHeight() << " pr "; + } - const AffineTransform inverseTransform; - BresenhamInterpolator xBresenham, yBresenham; + out << " clip newpath\n"; - TransformedImageSpanInterpolator (const TransformedImageSpanInterpolator&); - TransformedImageSpanInterpolator& operator= (const TransformedImageSpanInterpolator&); - }; + out << w << ' ' << h << " scale\n"; + out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; - TransformedImageSpanInterpolator interpolator; - const Image::BitmapData& destData; - const Image::BitmapData& srcData; - const int extraAlpha; - const bool betterQuality; - const float pixelOffset; - const int pixelOffsetInt, maxX, maxY; - int y; - DestPixelType* linePixels; - HeapBlock scratchBuffer; - int scratchSize; + writeImage (sourceImage, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); - TransformedImageFillEdgeTableRenderer (const TransformedImageFillEdgeTableRenderer&); - TransformedImageFillEdgeTableRenderer& operator= (const TransformedImageFillEdgeTableRenderer&); -}; + out << "false 3 colorimage grestore\n"; + needToClip = true; +} -class ClipRegionBase : public ReferenceCountedObject +void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) { -public: - ClipRegionBase() {} - virtual ~ClipRegionBase() {} - - typedef ReferenceCountedObjectPtr Ptr; + Path p; + p.addLineSegment (line, 1.0f); + fillPath (p, AffineTransform::identity); +} - virtual const Ptr clone() const = 0; - virtual const Ptr applyClipTo (const Ptr& target) const = 0; +void LowLevelGraphicsPostScriptRenderer::drawVerticalLine (const int x, float top, float bottom) +{ + drawLine (Line ((float) x, top, (float) x, bottom)); +} - virtual const Ptr clipToRectangle (const Rectangle& r) = 0; - virtual const Ptr clipToRectangleList (const RectangleList& r) = 0; - virtual const Ptr excludeClipRectangle (const Rectangle& r) = 0; - virtual const Ptr clipToPath (const Path& p, const AffineTransform& transform) = 0; - virtual const Ptr clipToEdgeTable (const EdgeTable& et) = 0; - virtual const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& t, const bool betterQuality) = 0; +void LowLevelGraphicsPostScriptRenderer::drawHorizontalLine (const int y, float left, float right) +{ + drawLine (Line (left, (float) y, right, (float) y)); +} - virtual bool clipRegionIntersects (const Rectangle& r) const = 0; - virtual const Rectangle getClipBounds() const = 0; +void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont) +{ + stateStack.getLast()->font = newFont; +} - virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const = 0; - virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const = 0; - virtual void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const = 0; - virtual void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const = 0; - virtual void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& t, bool betterQuality, bool tiledFill) const = 0; - virtual void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const = 0; +const Font LowLevelGraphicsPostScriptRenderer::getFont() +{ + return stateStack.getLast()->font; +} -protected: +void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) +{ + Path p; + Font& font = stateStack.getLast()->font; + font.getTypeface()->getOutlineForGlyph (glyphNumber, p); + fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); +} - template - static void renderImageTransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, - const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) - { - switch (destData.pixelFormat) - { - case Image::ARGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; +END_JUCE_NAMESPACE +/*** End of inlined file: juce_LowLevelGraphicsPostScriptRenderer.cpp ***/ - case Image::RGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; - default: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; - } - } +/*** Start of inlined file: juce_LowLevelGraphicsSoftwareRenderer.cpp ***/ +BEGIN_JUCE_NAMESPACE - template - static void renderImageUntransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) - { - switch (destData.pixelFormat) - { - case Image::ARGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; +#if (JUCE_WINDOWS || JUCE_LINUX) && ! JUCE_64BIT + #define JUCE_USE_SSE_INSTRUCTIONS 1 +#endif - case Image::RGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4127) // "expression is constant" warning - default: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; - } - } + #if JUCE_DEBUG + #pragma optimize ("t", on) // optimise just this file, to avoid sluggish graphics when debugging + #pragma warning (disable: 4714) // warning about forcedinline methods not being inlined + #endif +#endif - template - static void renderSolidFill (Iterator& iter, const Image::BitmapData& destData, const PixelARGB& fillColour, const bool replaceContents, DestPixelType*) - { - jassert (destData.pixelStride == sizeof (DestPixelType)); - if (replaceContents) - { - SolidColourEdgeTableRenderer r (destData, fillColour); - iter.iterate (r); - } - else - { - SolidColourEdgeTableRenderer r (destData, fillColour); - iter.iterate (r); - } - } +namespace SoftwareRendererClasses +{ - template - static void renderGradient (Iterator& iter, const Image::BitmapData& destData, const ColourGradient& g, const AffineTransform& transform, - const PixelARGB* const lookupTable, const int numLookupEntries, const bool isIdentity, DestPixelType*) +template +class SolidColourEdgeTableRenderer +{ +public: + SolidColourEdgeTableRenderer (const Image::BitmapData& data_, const PixelARGB& colour) + : data (data_), + sourceColour (colour) { - jassert (destData.pixelStride == sizeof (DestPixelType)); - - if (g.isRadial) - { - if (isIdentity) - { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); - } - else - { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); - } - } - else + if (sizeof (PixelType) == 3) { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); + areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() + && sourceColour.getGreen() == sourceColour.getBlue(); + filler[0].set (sourceColour); + filler[1].set (sourceColour); + filler[2].set (sourceColour); + filler[3].set (sourceColour); } } -}; - -class ClipRegion_EdgeTable : public ClipRegionBase -{ -public: - ClipRegion_EdgeTable (const EdgeTable& e) : edgeTable (e) {} - ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const RectangleList& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const Rectangle& bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} - ClipRegion_EdgeTable (const ClipRegion_EdgeTable& other) : edgeTable (other.edgeTable) {} - ~ClipRegion_EdgeTable() {} - const Ptr clone() const + forcedinline void setEdgeTableYPos (const int y) throw() { - return new ClipRegion_EdgeTable (*this); + linePixels = (PixelType*) data.getLinePointer (y); } - const Ptr applyClipTo (const Ptr& target) const + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() { - return target->clipToEdgeTable (edgeTable); + if (replaceExisting) + linePixels[x].set (sourceColour); + else + linePixels[x].blend (sourceColour, alphaLevel); } - const Ptr clipToRectangle (const Rectangle& r) + forcedinline void handleEdgeTablePixelFull (const int x) const throw() { - edgeTable.clipToRectangle (r); - return edgeTable.isEmpty() ? 0 : this; + if (replaceExisting) + linePixels[x].set (sourceColour); + else + linePixels[x].blend (sourceColour); } - const Ptr clipToRectangleList (const RectangleList& r) + forcedinline void handleEdgeTableLine (const int x, const int width, const int alphaLevel) const throw() { - RectangleList inverse (edgeTable.getMaximumBounds()); + PixelARGB p (sourceColour); + p.multiplyAlpha (alphaLevel); - if (inverse.subtract (r)) - for (RectangleList::Iterator iter (inverse); iter.next();) - edgeTable.excludeRectangle (*iter.getRectangle()); + PixelType* dest = linePixels + x; - return edgeTable.isEmpty() ? 0 : this; + if (replaceExisting || p.getAlpha() >= 0xff) + replaceLine (dest, p, width); + else + blendLine (dest, p, width); } - const Ptr excludeClipRectangle (const Rectangle& r) + forcedinline void handleEdgeTableLineFull (const int x, const int width) const throw() { - edgeTable.excludeRectangle (r); - return edgeTable.isEmpty() ? 0 : this; - } + PixelType* dest = linePixels + x; - const Ptr clipToPath (const Path& p, const AffineTransform& transform) - { - EdgeTable et (edgeTable.getMaximumBounds(), p, transform); - edgeTable.clipToEdgeTable (et); - return edgeTable.isEmpty() ? 0 : this; + if (replaceExisting || sourceColour.getAlpha() >= 0xff) + replaceLine (dest, sourceColour, width); + else + blendLine (dest, sourceColour, width); } - const Ptr clipToEdgeTable (const EdgeTable& et) +private: + const Image::BitmapData& data; + PixelType* linePixels; + PixelARGB sourceColour; + PixelRGB filler [4]; + bool areRGBComponentsEqual; + + inline void blendLine (PixelType* dest, const PixelARGB& colour, int width) const throw() { - edgeTable.clipToEdgeTable (et); - return edgeTable.isEmpty() ? 0 : this; + do + { + dest->blend (colour); + ++dest; + } while (--width > 0); } - const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& transform, const bool betterQuality) + forcedinline void replaceLine (PixelRGB* dest, const PixelARGB& colour, int width) const throw() { - const Image::BitmapData srcData (image, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); - - if (transform.isOnlyTranslation()) + if (areRGBComponentsEqual) // if all the component values are the same, we can cheat.. { - // If our translation doesn't involve any distortion, just use a simple blit.. - const int tx = (int) (transform.getTranslationX() * 256.0f); - const int ty = (int) (transform.getTranslationY() * 256.0f); - - if ((! betterQuality) || ((tx | ty) & 224) == 0) + memset (dest, colour.getRed(), width * 3); + } + else + { + if (width >> 5) { - const int imageX = ((tx + 128) >> 8); - const int imageY = ((ty + 128) >> 8); + const int* const intFiller = (const int*) filler; - if (image.getFormat() == Image::ARGB) - straightClipImage (srcData, imageX, imageY, (PixelARGB*) 0); - else - straightClipImage (srcData, imageX, imageY, (PixelAlpha*) 0); + while (width > 8 && (((pointer_sized_int) dest) & 7) != 0) + { + dest->set (colour); + ++dest; + --width; + } - return edgeTable.isEmpty() ? 0 : this; + while (width > 4) + { + ((int*) dest) [0] = intFiller[0]; + ((int*) dest) [1] = intFiller[1]; + ((int*) dest) [2] = intFiller[2]; + dest = (PixelRGB*) (((uint8*) dest) + 12); + width -= 4; + } } - } - - if (transform.isSingularity()) - return 0; - - { - Path p; - p.addRectangle (0, 0, (float) srcData.width, (float) srcData.height); - EdgeTable et2 (edgeTable.getMaximumBounds(), p, transform); - edgeTable.clipToEdgeTable (et2); - } - if (! edgeTable.isEmpty()) - { - if (image.getFormat() == Image::ARGB) - transformedClipImage (srcData, transform, betterQuality, (PixelARGB*) 0); - else - transformedClipImage (srcData, transform, betterQuality, (PixelAlpha*) 0); + while (--width >= 0) + { + dest->set (colour); + ++dest; + } } - - return edgeTable.isEmpty() ? 0 : this; } - bool clipRegionIntersects (const Rectangle& r) const + forcedinline void replaceLine (PixelAlpha* const dest, const PixelARGB& colour, int const width) const throw() { - return edgeTable.getMaximumBounds().intersects (r); + memset (dest, colour.getAlpha(), width); } - const Rectangle getClipBounds() const + forcedinline void replaceLine (PixelARGB* dest, const PixelARGB& colour, int width) const throw() { - return edgeTable.getMaximumBounds(); + do + { + dest->set (colour); + ++dest; + + } while (--width > 0); } - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + SolidColourEdgeTableRenderer (const SolidColourEdgeTableRenderer&); + SolidColourEdgeTableRenderer& operator= (const SolidColourEdgeTableRenderer&); +}; + +class LinearGradientPixelGenerator +{ +public: + LinearGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, const PixelARGB* const lookupTable_, const int numEntries_) + : lookupTable (lookupTable_), numEntries (numEntries_) { - const Rectangle totalClip (edgeTable.getMaximumBounds()); - const Rectangle clipped (totalClip.getIntersection (area)); + jassert (numEntries_ >= 0); + Point p1 (gradient.point1); + Point p2 (gradient.point2); - if (! clipped.isEmpty()) + if (! transform.isIdentity()) { - ClipRegion_EdgeTable et (clipped); - et.edgeTable.clipToEdgeTable (edgeTable); - et.fillAllWithColour (destData, colour, replaceContents); - } - } + const Line l (p2, p1); + Point p3 = l.getPointAlongLine (0.0f, 100.0f); - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const - { - const Rectangle totalClip (edgeTable.getMaximumBounds().toFloat()); - const Rectangle clipped (totalClip.getIntersection (area)); + p1.applyTransform (transform); + p2.applyTransform (transform); + p3.applyTransform (transform); + + p2 = Line (p2, p3).findNearestPointTo (p1); + } - if (! clipped.isEmpty()) + vertical = std::abs (p1.getX() - p2.getX()) < 0.001f; + horizontal = std::abs (p1.getY() - p2.getY()) < 0.001f; + + if (vertical) { - ClipRegion_EdgeTable et (clipped); - et.edgeTable.clipToEdgeTable (edgeTable); - et.fillAllWithColour (destData, colour, false); + scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.getY() - p1.getY())); + start = roundToInt (p1.getY() * scale); } - } - - void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const - { - switch (destData.pixelFormat) + else if (horizontal) { - case Image::ARGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelAlpha*) 0); break; + scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.getX() - p1.getX())); + start = roundToInt (p1.getX() * scale); } - } - - void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const - { - HeapBlock lookupTable; - const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); - jassert (numLookupEntries > 0); - - switch (destData.pixelFormat) + else { - case Image::ARGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; - case Image::RGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; - default: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + grad = (p2.getY() - p1.getY()) / (double) (p1.getX() - p2.getX()); + yTerm = p1.getY() - p1.getX() / grad; + scale = roundToInt ((numEntries << (int) numScaleBits) / (yTerm * grad - (p2.getY() * grad - p2.getX()))); + grad *= scale; } } - void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const + forcedinline void setY (const int y) throw() { - renderImageTransformedInternal (edgeTable, destData, srcData, alpha, transform, betterQuality, tiledFill); + if (vertical) + linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> (int) numScaleBits)]; + else if (! horizontal) + start = roundToInt ((y - yTerm) * grad); } - void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const + inline const PixelARGB getPixel (const int x) const throw() { - renderImageUntransformedInternal (edgeTable, destData, srcData, alpha, x, y, tiledFill); + return vertical ? linePix + : lookupTable [jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; } - EdgeTable edgeTable; - private: + const PixelARGB* const lookupTable; + const int numEntries; + PixelARGB linePix; + int start, scale; + double grad, yTerm; + bool vertical, horizontal; + enum { numScaleBits = 12 }; - template - void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const bool betterQuality, const SrcPixelType*) - { - TransformedImageFillEdgeTableRenderer renderer (srcData, srcData, transform, 255, betterQuality); + LinearGradientPixelGenerator (const LinearGradientPixelGenerator&); + LinearGradientPixelGenerator& operator= (const LinearGradientPixelGenerator&); +}; - for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) - renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), - edgeTable.getMaximumBounds().getWidth()); +class RadialGradientPixelGenerator +{ +public: + RadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform&, + const PixelARGB* const lookupTable_, const int numEntries_) + : lookupTable (lookupTable_), + numEntries (numEntries_), + gx1 (gradient.point1.getX()), + gy1 (gradient.point1.getY()) + { + jassert (numEntries_ >= 0); + const Point diff (gradient.point1 - gradient.point2); + maxDist = diff.getX() * diff.getX() + diff.getY() * diff.getY(); + invScale = numEntries / std::sqrt (maxDist); + jassert (roundToInt (std::sqrt (maxDist) * invScale) <= numEntries); } - template - void straightClipImage (const Image::BitmapData& srcData, int imageX, int imageY, const SrcPixelType*) + forcedinline void setY (const int y) throw() { - Rectangle r (imageX, imageY, srcData.width, srcData.height); - edgeTable.clipToRectangle (r); + dy = y - gy1; + dy *= dy; + } - ImageFillEdgeTableRenderer renderer (srcData, srcData, 255, imageX, imageY); + inline const PixelARGB getPixel (const int px) const throw() + { + double x = px - gx1; + x *= x; + x += dy; - for (int y = 0; y < r.getHeight(); ++y) - renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); + return lookupTable [x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; } - ClipRegion_EdgeTable& operator= (const ClipRegion_EdgeTable&); +protected: + const PixelARGB* const lookupTable; + const int numEntries; + const double gx1, gy1; + double maxDist, invScale, dy; + + RadialGradientPixelGenerator (const RadialGradientPixelGenerator&); + RadialGradientPixelGenerator& operator= (const RadialGradientPixelGenerator&); }; -class ClipRegion_RectangleList : public ClipRegionBase +class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator { public: - ClipRegion_RectangleList (const Rectangle& r) : clip (r) {} - ClipRegion_RectangleList (const RectangleList& r) : clip (r) {} - ClipRegion_RectangleList (const ClipRegion_RectangleList& other) : clip (other.clip) {} - ~ClipRegion_RectangleList() {} - - const Ptr clone() const + TransformedRadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const lookupTable_, const int numEntries_) + : RadialGradientPixelGenerator (gradient, transform, lookupTable_, numEntries_), + inverseTransform (transform.inverted()) { - return new ClipRegion_RectangleList (*this); + tM10 = inverseTransform.mat10; + tM00 = inverseTransform.mat00; } - const Ptr applyClipTo (const Ptr& target) const + forcedinline void setY (const int y) throw() { - return target->clipToRectangleList (clip); + lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; + lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; } - const Ptr clipToRectangle (const Rectangle& r) + inline const PixelARGB getPixel (const int px) const throw() { - clip.clipTo (r); - return clip.isEmpty() ? 0 : this; - } + double x = px; + const double y = tM10 * x + lineYM11; + x = tM00 * x + lineYM01; + x *= x; + x += y * y; - const Ptr clipToRectangleList (const RectangleList& r) - { - clip.clipTo (r); - return clip.isEmpty() ? 0 : this; + if (x >= maxDist) + return lookupTable [numEntries]; + else + return lookupTable [jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; } - const Ptr excludeClipRectangle (const Rectangle& r) - { - clip.subtract (r); - return clip.isEmpty() ? 0 : this; - } +private: + double tM10, tM00, lineYM01, lineYM11; + const AffineTransform inverseTransform; - const Ptr clipToPath (const Path& p, const AffineTransform& transform) - { - return Ptr (new ClipRegion_EdgeTable (clip))->clipToPath (p, transform); - } + TransformedRadialGradientPixelGenerator (const TransformedRadialGradientPixelGenerator&); + TransformedRadialGradientPixelGenerator& operator= (const TransformedRadialGradientPixelGenerator&); +}; - const Ptr clipToEdgeTable (const EdgeTable& et) +template +class GradientEdgeTableRenderer : public GradientType +{ +public: + GradientEdgeTableRenderer (const Image::BitmapData& destData_, const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const lookupTable_, const int numEntries_) + : GradientType (gradient, transform, lookupTable_, numEntries_ - 1), + destData (destData_) { - return Ptr (new ClipRegion_EdgeTable (clip))->clipToEdgeTable (et); } - const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& transform, const bool betterQuality) + forcedinline void setEdgeTableYPos (const int y) throw() { - return Ptr (new ClipRegion_EdgeTable (clip))->clipToImageAlpha (image, srcClip, transform, betterQuality); + linePixels = (PixelType*) destData.getLinePointer (y); + GradientType::setY (y); } - bool clipRegionIntersects (const Rectangle& r) const + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw() { - return clip.intersects (r); + linePixels[x].blend (GradientType::getPixel (x), alphaLevel); } - const Rectangle getClipBounds() const + forcedinline void handleEdgeTablePixelFull (const int x) const throw() { - return clip.getBounds(); + linePixels[x].blend (GradientType::getPixel (x)); } - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + void handleEdgeTableLine (int x, int width, const int alphaLevel) const throw() { - SubRectangleIterator iter (clip, area); + PixelType* dest = linePixels + x; - switch (destData.pixelFormat) + if (alphaLevel < 0xff) { - case Image::ARGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) 0); break; + do + { + (dest++)->blend (GradientType::getPixel (x++), alphaLevel); + } while (--width > 0); } - } - - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const - { - SubRectangleIteratorFloat iter (clip, area); - - switch (destData.pixelFormat) + else { - case Image::ARGB: renderSolidFill (iter, destData, colour, false, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (iter, destData, colour, false, (PixelRGB*) 0); break; - default: renderSolidFill (iter, destData, colour, false, (PixelAlpha*) 0); break; + do + { + (dest++)->blend (GradientType::getPixel (x++)); + } while (--width > 0); } } - void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const + void handleEdgeTableLineFull (int x, int width) const throw() { - switch (destData.pixelFormat) + PixelType* dest = linePixels + x; + + do { - case Image::ARGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (*this, destData, colour, replaceContents, (PixelAlpha*) 0); break; - } + (dest++)->blend (GradientType::getPixel (x++)); + } while (--width > 0); } - void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const - { - HeapBlock lookupTable; - const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); - jassert (numLookupEntries > 0); +private: + const Image::BitmapData& destData; + PixelType* linePixels; - switch (destData.pixelFormat) - { - case Image::ARGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; - case Image::RGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; - default: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; - } - } + GradientEdgeTableRenderer (const GradientEdgeTableRenderer&); + GradientEdgeTableRenderer& operator= (const GradientEdgeTableRenderer&); +}; - void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const - { - renderImageTransformedInternal (*this, destData, srcData, alpha, transform, betterQuality, tiledFill); - } +static forcedinline int safeModulo (int n, const int divisor) throw() +{ + jassert (divisor > 0); + n %= divisor; + return (n < 0) ? (n + divisor) : n; +} - void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const +template +class ImageFillEdgeTableRenderer +{ +public: + ImageFillEdgeTableRenderer (const Image::BitmapData& destData_, + const Image::BitmapData& srcData_, + const int extraAlpha_, + const int x, const int y) + : destData (destData_), + srcData (srcData_), + extraAlpha (extraAlpha_ + 1), + xOffset (repeatPattern ? safeModulo (x, srcData_.width) - srcData_.width : x), + yOffset (repeatPattern ? safeModulo (y, srcData_.height) - srcData_.height : y) { - renderImageUntransformedInternal (*this, destData, srcData, alpha, x, y, tiledFill); } - RectangleList clip; - - template - void iterate (Renderer& r) const throw() + forcedinline void setEdgeTableYPos (int y) throw() { - RectangleList::Iterator iter (clip); + linePixels = (DestPixelType*) destData.getLinePointer (y); - while (iter.next()) + y -= yOffset; + if (repeatPattern) { - const Rectangle rect (*iter.getRectangle()); - const int x = rect.getX(); - const int w = rect.getWidth(); - jassert (w > 0); - const int bottom = rect.getBottom(); - - for (int y = rect.getY(); y < bottom; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTableLineFull (x, w); - } + jassert (y >= 0); + y %= srcData.height; } - } -private: + sourceLineStart = (SrcPixelType*) srcData.getLinePointer (y); + } - class SubRectangleIterator + forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const throw() { - public: - SubRectangleIterator (const RectangleList& clip_, const Rectangle& area_) - : clip (clip_), area (area_) - { - } - - template - void iterate (Renderer& r) const throw() - { - RectangleList::Iterator iter (clip); - - while (iter.next()) - { - const Rectangle rect (iter.getRectangle()->getIntersection (area)); + alphaLevel = (alphaLevel * extraAlpha) >> 8; - if (! rect.isEmpty()) - { - const int x = rect.getX(); - const int w = rect.getWidth(); - const int bottom = rect.getBottom(); + linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], alphaLevel); + } - for (int y = rect.getY(); y < bottom; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTableLineFull (x, w); - } - } - } - } + forcedinline void handleEdgeTablePixelFull (const int x) const throw() + { + linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], extraAlpha); + } - private: - const RectangleList& clip; - const Rectangle area; + void handleEdgeTableLine (int x, int width, int alphaLevel) const throw() + { + DestPixelType* dest = linePixels + x; + alphaLevel = (alphaLevel * extraAlpha) >> 8; + x -= xOffset; - SubRectangleIterator (const SubRectangleIterator&); - SubRectangleIterator& operator= (const SubRectangleIterator&); - }; + jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - class SubRectangleIteratorFloat - { - public: - SubRectangleIteratorFloat (const RectangleList& clip_, const Rectangle& area_) - : clip (clip_), area (area_) + if (alphaLevel < 0xfe) { + do + { + dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], alphaLevel); + } while (--width > 0); } - - template - void iterate (Renderer& r) const throw() + else { - int left = roundToInt (area.getX() * 256.0f); - int top = roundToInt (area.getY() * 256.0f); - int right = roundToInt (area.getRight() * 256.0f); - int bottom = roundToInt (area.getBottom() * 256.0f); - - int totalTop, totalLeft, totalBottom, totalRight; - int topAlpha, leftAlpha, bottomAlpha, rightAlpha; - - if ((top >> 8) == (bottom >> 8)) - { - topAlpha = bottom - top; - bottomAlpha = 0; - totalTop = top >> 8; - totalBottom = bottom = top = totalTop + 1; - } - else + if (repeatPattern) { - if ((top & 255) == 0) - { - topAlpha = 0; - top = totalTop = (top >> 8); - } - else + do { - topAlpha = 255 - (top & 255); - totalTop = (top >> 8); - top = totalTop + 1; - } - - bottomAlpha = bottom & 255; - bottom >>= 8; - totalBottom = bottom + (bottomAlpha != 0 ? 1 : 0); - } - - if ((left >> 8) == (right >> 8)) - { - leftAlpha = right - left; - rightAlpha = 0; - totalLeft = (left >> 8); - totalRight = right = left = totalLeft + 1; + dest++ ->blend (sourceLineStart [x++ % srcData.width]); + } while (--width > 0); } else { - if ((left & 255) == 0) - { - leftAlpha = 0; - left = totalLeft = (left >> 8); - } - else - { - leftAlpha = 255 - (left & 255); - totalLeft = (left >> 8); - left = totalLeft + 1; - } - - rightAlpha = right & 255; - right >>= 8; - totalRight = right + (rightAlpha != 0 ? 1 : 0); + copyRow (dest, sourceLineStart + x, width); } + } + } - RectangleList::Iterator iter (clip); - - while (iter.next()) - { - const int clipLeft = iter.getRectangle()->getX(); - const int clipRight = iter.getRectangle()->getRight(); - const int clipTop = iter.getRectangle()->getY(); - const int clipBottom = iter.getRectangle()->getBottom(); - - if (totalBottom > clipTop && totalTop < clipBottom && totalRight > clipLeft && totalLeft < clipRight) - { - if (right - left == 1 && leftAlpha + rightAlpha == 0) // special case for 1-pix vertical lines - { - if (topAlpha != 0 && totalTop >= clipTop) - { - r.setEdgeTableYPos (totalTop); - r.handleEdgeTablePixel (left, topAlpha); - } - - const int endY = jmin (bottom, clipBottom); - for (int y = jmax (clipTop, top); y < endY; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTablePixelFull (left); - } - - if (bottomAlpha != 0 && bottom < clipBottom) - { - r.setEdgeTableYPos (bottom); - r.handleEdgeTablePixel (left, bottomAlpha); - } - } - else - { - const int clippedLeft = jmax (left, clipLeft); - const int clippedWidth = jmin (right, clipRight) - clippedLeft; - const bool doLeftAlpha = leftAlpha != 0 && totalLeft >= clipLeft; - const bool doRightAlpha = rightAlpha != 0 && right < clipRight; - - if (topAlpha != 0 && totalTop >= clipTop) - { - r.setEdgeTableYPos (totalTop); - - if (doLeftAlpha) - r.handleEdgeTablePixel (totalLeft, (leftAlpha * topAlpha) >> 8); - - if (clippedWidth > 0) - r.handleEdgeTableLine (clippedLeft, clippedWidth, topAlpha); - - if (doRightAlpha) - r.handleEdgeTablePixel (right, (rightAlpha * topAlpha) >> 8); - } - - const int endY = jmin (bottom, clipBottom); - for (int y = jmax (clipTop, top); y < endY; ++y) - { - r.setEdgeTableYPos (y); - - if (doLeftAlpha) - r.handleEdgeTablePixel (totalLeft, leftAlpha); - - if (clippedWidth > 0) - r.handleEdgeTableLineFull (clippedLeft, clippedWidth); - - if (doRightAlpha) - r.handleEdgeTablePixel (right, rightAlpha); - } - - if (bottomAlpha != 0 && bottom < clipBottom) - { - r.setEdgeTableYPos (bottom); - - if (doLeftAlpha) - r.handleEdgeTablePixel (totalLeft, (leftAlpha * bottomAlpha) >> 8); + void handleEdgeTableLineFull (int x, int width) const throw() + { + DestPixelType* dest = linePixels + x; + x -= xOffset; - if (clippedWidth > 0) - r.handleEdgeTableLine (clippedLeft, clippedWidth, bottomAlpha); + jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - if (doRightAlpha) - r.handleEdgeTablePixel (right, (rightAlpha * bottomAlpha) >> 8); - } - } - } + if (extraAlpha < 0xfe) + { + do + { + dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], extraAlpha); + } while (--width > 0); + } + else + { + if (repeatPattern) + { + do + { + dest++ ->blend (sourceLineStart [x++ % srcData.width]); + } while (--width > 0); + } + else + { + copyRow (dest, sourceLineStart + x, width); } } + } - private: - const RectangleList& clip; - const Rectangle& area; + void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) + { + jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); + SrcPixelType* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); + uint8* mask = (uint8*) (s + x - xOffset); - SubRectangleIteratorFloat (const SubRectangleIteratorFloat&); - SubRectangleIteratorFloat& operator= (const SubRectangleIteratorFloat&); - }; + if (sizeof (SrcPixelType) == sizeof (PixelARGB)) + mask += PixelARGB::indexA; - ClipRegion_RectangleList& operator= (const ClipRegion_RectangleList&); -}; + et.clipLineToMask (x, y, mask, sizeof (SrcPixelType), width); + } -} +private: + const Image::BitmapData& destData; + const Image::BitmapData& srcData; + const int extraAlpha, xOffset, yOffset; + DestPixelType* linePixels; + SrcPixelType* sourceLineStart; -class LowLevelGraphicsSoftwareRenderer::SavedState -{ -public: - SavedState (const Rectangle& clip_, const int xOffset_, const int yOffset_) - : clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), - xOffset (xOffset_), yOffset (yOffset_), interpolationQuality (Graphics::mediumResamplingQuality) + template + forcedinline static void copyRow (PixelType1* dest, PixelType2* src, int width) throw() { + do + { + dest++ ->blend (*src++); + } while (--width > 0); } - SavedState (const RectangleList& clip_, const int xOffset_, const int yOffset_) - : clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), - xOffset (xOffset_), yOffset (yOffset_), interpolationQuality (Graphics::mediumResamplingQuality) + forcedinline static void copyRow (PixelRGB* dest, PixelRGB* src, int width) throw() { + memcpy (dest, src, width * sizeof (PixelRGB)); } - SavedState (const SavedState& other) - : clip (other.clip), xOffset (other.xOffset), yOffset (other.yOffset), font (other.font), - fillType (other.fillType), interpolationQuality (other.interpolationQuality) - { - } + ImageFillEdgeTableRenderer (const ImageFillEdgeTableRenderer&); + ImageFillEdgeTableRenderer& operator= (const ImageFillEdgeTableRenderer&); +}; - ~SavedState() +template +class TransformedImageFillEdgeTableRenderer +{ +public: + TransformedImageFillEdgeTableRenderer (const Image::BitmapData& destData_, + const Image::BitmapData& srcData_, + const AffineTransform& transform, + const int extraAlpha_, + const bool betterQuality_) + : interpolator (transform), + destData (destData_), + srcData (srcData_), + extraAlpha (extraAlpha_ + 1), + betterQuality (betterQuality_), + pixelOffset (betterQuality_ ? 0.5f : 0.0f), + pixelOffsetInt (betterQuality_ ? -128 : 0), + maxX (srcData_.width - 1), + maxY (srcData_.height - 1), + scratchSize (2048) { + scratchBuffer.malloc (scratchSize); } - void setOrigin (const int x, const int y) throw() + ~TransformedImageFillEdgeTableRenderer() { - xOffset += x; - yOffset += y; } - bool clipToRectangle (const Rectangle& r) + forcedinline void setEdgeTableYPos (const int newY) throw() { - if (clip != 0) - { - cloneClipIfMultiplyReferenced(); - clip = clip->clipToRectangle (r.translated (xOffset, yOffset)); - } - - return clip != 0; + y = newY; + linePixels = (DestPixelType*) destData.getLinePointer (newY); } - bool clipToRectangleList (const RectangleList& r) + forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) throw() { - if (clip != 0) - { - cloneClipIfMultiplyReferenced(); + alphaLevel *= extraAlpha; + alphaLevel >>= 8; - RectangleList offsetList (r); - offsetList.offsetAll (xOffset, yOffset); - clip = clip->clipToRectangleList (offsetList); - } + SrcPixelType p; + generate (&p, x, 1); - return clip != 0; + linePixels[x].blend (p, alphaLevel); } - bool excludeClipRectangle (const Rectangle& r) + forcedinline void handleEdgeTablePixelFull (const int x) throw() { - if (clip != 0) - { - cloneClipIfMultiplyReferenced(); - clip = clip->excludeClipRectangle (r.translated (xOffset, yOffset)); - } - - return clip != 0; - } + SrcPixelType p; + generate (&p, x, 1); - void clipToPath (const Path& p, const AffineTransform& transform) - { - if (clip != 0) - { - cloneClipIfMultiplyReferenced(); - clip = clip->clipToPath (p, transform.translated ((float) xOffset, (float) yOffset)); - } + linePixels[x].blend (p, extraAlpha); } - void clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& t) + void handleEdgeTableLine (const int x, int width, int alphaLevel) throw() { - if (clip != 0) + if (width > scratchSize) { - if (image.hasAlphaChannel()) - { - cloneClipIfMultiplyReferenced(); - clip = clip->clipToImageAlpha (image, srcClip, t.translated ((float) xOffset, (float) yOffset), - interpolationQuality != Graphics::lowResamplingQuality); - } - else - { - Path p; - p.addRectangle (srcClip); - clipToPath (p, t); - } + scratchSize = width; + scratchBuffer.malloc (scratchSize); } - } - bool clipRegionIntersects (const Rectangle& r) const - { - return clip != 0 && clip->clipRegionIntersects (r.translated (xOffset, yOffset)); - } + SrcPixelType* span = scratchBuffer; + generate (span, x, width); - const Rectangle getClipBounds() const - { - return clip == 0 ? Rectangle() : clip->getClipBounds().translated (-xOffset, -yOffset); - } + DestPixelType* dest = linePixels + x; + alphaLevel *= extraAlpha; + alphaLevel >>= 8; - void fillRect (Image& image, const Rectangle& r, const bool replaceContents) - { - if (clip != 0) + if (alphaLevel < 0xfe) { - if (fillType.isColour()) - { - Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); - clip->fillRectWithColour (destData, r.translated (xOffset, yOffset), fillType.colour.getPixelARGB(), replaceContents); - } - else + do { - const Rectangle totalClip (clip->getClipBounds()); - const Rectangle clipped (totalClip.getIntersection (r.translated (xOffset, yOffset))); - - if (! clipped.isEmpty()) - fillShape (image, new SoftwareRendererClasses::ClipRegion_RectangleList (clipped), false); - } + dest++ ->blend (*span++, alphaLevel); + } while (--width > 0); } - } - - void fillRect (Image& image, const Rectangle& r) - { - if (clip != 0) + else { - if (fillType.isColour()) - { - Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); - clip->fillRectWithColour (destData, r.translated ((float) xOffset, (float) yOffset), fillType.colour.getPixelARGB()); - } - else + do { - const Rectangle totalClip (clip->getClipBounds().toFloat()); - const Rectangle clipped (totalClip.getIntersection (r.translated ((float) xOffset, (float) yOffset))); - - if (! clipped.isEmpty()) - fillShape (image, new SoftwareRendererClasses::ClipRegion_EdgeTable (clipped), false); - } + dest++ ->blend (*span++); + } while (--width > 0); } } - void fillPath (Image& image, const Path& path, const AffineTransform& transform) + forcedinline void handleEdgeTableLineFull (const int x, int width) throw() { - if (clip != 0) - fillShape (image, new SoftwareRendererClasses::ClipRegion_EdgeTable (clip->getClipBounds(), path, transform.translated ((float) xOffset, (float) yOffset)), false); + handleEdgeTableLine (x, width, 255); } - void fillEdgeTable (Image& image, const EdgeTable& edgeTable, const float x, const int y) + void clipEdgeTableLine (EdgeTable& et, int x, int y_, int width) { - if (clip != 0) + if (width > scratchSize) { - SoftwareRendererClasses::ClipRegion_EdgeTable* edgeTableClip = new SoftwareRendererClasses::ClipRegion_EdgeTable (edgeTable); - SoftwareRendererClasses::ClipRegionBase::Ptr shapeToFill (edgeTableClip); - edgeTableClip->edgeTable.translate (x + xOffset, y + yOffset); - fillShape (image, shapeToFill, false); + scratchSize = width; + scratchBuffer.malloc (scratchSize); } - } - void fillShape (Image& image, SoftwareRendererClasses::ClipRegionBase::Ptr shapeToFill, const bool replaceContents) - { - jassert (clip != 0); + y = y_; + generate (scratchBuffer, x, width); - shapeToFill = clip->applyClipTo (shapeToFill); + et.clipLineToMask (x, y_, + reinterpret_cast (scratchBuffer.getData()) + SrcPixelType::indexA, + sizeof (SrcPixelType), width); + } - if (shapeToFill != 0) - { - Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); +private: - if (fillType.isGradient()) - { - jassert (! replaceContents); // that option is just for solid colours + void generate (PixelARGB* dest, const int x, int numPixels) throw() + { + this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); - ColourGradient g2 (*(fillType.gradient)); - g2.multiplyOpacity (fillType.getOpacity()); - g2.point1.addXY (-0.5f, -0.5f); - g2.point2.addXY (-0.5f, -0.5f); - AffineTransform transform (fillType.transform.translated ((float) xOffset, (float) yOffset)); - const bool isIdentity = transform.isOnlyTranslation(); + do + { + int hiResX, hiResY; + this->interpolator.next (hiResX, hiResY); + hiResX += pixelOffsetInt; + hiResY += pixelOffsetInt; - if (isIdentity) - { - // If our translation doesn't involve any distortion, we can speed it up.. - g2.point1.applyTransform (transform); - g2.point2.applyTransform (transform); - transform = AffineTransform::identity; - } + int loResX = hiResX >> 8; + int loResY = hiResY >> 8; - shapeToFill->fillAllWithGradient (destData, g2, transform, isIdentity); - } - else if (fillType.isTiledImage()) + if (repeatPattern) { - renderImage (image, fillType.image, fillType.image.getBounds(), fillType.transform, shapeToFill); + loResX = safeModulo (loResX, srcData.width); + loResY = safeModulo (loResY, srcData.height); } - else + + if (betterQuality + && ((unsigned int) loResX) < (unsigned int) maxX + && ((unsigned int) loResY) < (unsigned int) maxY) { - shapeToFill->fillAllWithColour (destData, fillType.colour.getPixelARGB(), replaceContents); - } - } - } + uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; + hiResX &= 255; + hiResY &= 255; - void renderImage (Image& destImage, const Image& sourceImage, const Rectangle& srcClip, - const AffineTransform& t, const SoftwareRendererClasses::ClipRegionBase* const tiledFillClipRegion) - { - const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); + const uint8* src = this->srcData.getPixelPointer (loResX, loResY); - const Image::BitmapData destData (destImage, 0, 0, destImage.getWidth(), destImage.getHeight(), true); - const Image::BitmapData srcData (sourceImage, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); - const int alpha = fillType.colour.getAlpha(); - const bool betterQuality = (interpolationQuality != Graphics::lowResamplingQuality); + uint32 weight = (256 - hiResX) * (256 - hiResY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; - if (transform.isOnlyTranslation()) - { - // If our translation doesn't involve any distortion, just use a simple blit.. - int tx = (int) (transform.getTranslationX() * 256.0f); - int ty = (int) (transform.getTranslationY() * 256.0f); + weight = hiResX * (256 - hiResY); + c[0] += weight * src[4]; + c[1] += weight * src[5]; + c[2] += weight * src[6]; + c[3] += weight * src[7]; - if ((! betterQuality) || ((tx | ty) & 224) == 0) - { - tx = ((tx + 128) >> 8); - ty = ((ty + 128) >> 8); + src += this->srcData.lineStride; - if (tiledFillClipRegion != 0) - { - tiledFillClipRegion->renderImageUntransformed (destData, srcData, alpha, tx, ty, true); - } - else - { - SoftwareRendererClasses::ClipRegionBase::Ptr c (new SoftwareRendererClasses::ClipRegion_EdgeTable (Rectangle (tx, ty, srcClip.getWidth(), srcClip.getHeight()).getIntersection (destImage.getBounds()))); - c = clip->applyClipTo (c); + weight = (256 - hiResX) * hiResY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; - if (c != 0) - c->renderImageUntransformed (destData, srcData, alpha, tx, ty, false); - } + weight = hiResX * hiResY; + c[0] += weight * src[4]; + c[1] += weight * src[5]; + c[2] += weight * src[6]; + c[3] += weight * src[7]; - return; + dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 16), + (uint8) (c[PixelARGB::indexR] >> 16), + (uint8) (c[PixelARGB::indexG] >> 16), + (uint8) (c[PixelARGB::indexB] >> 16)); } - } - - if (transform.isSingularity()) - return; + else + { + if (! repeatPattern) + { + // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable + if (loResX < 0) loResX = 0; + if (loResY < 0) loResY = 0; + if (loResX > maxX) loResX = maxX; + if (loResY > maxY) loResY = maxY; + } - if (tiledFillClipRegion != 0) - { - tiledFillClipRegion->renderImageTransformed (destData, srcData, alpha, transform, betterQuality, true); - } - else - { - Path p; - p.addRectangle (srcClip); + dest->set (*(const PixelARGB*) this->srcData.getPixelPointer (loResX, loResY)); + } - SoftwareRendererClasses::ClipRegionBase::Ptr c (clip->clone()); - c = c->clipToPath (p, transform); + ++dest; - if (c != 0) - c->renderImageTransformed (destData, srcData, alpha, transform, betterQuality, false); - } + } while (--numPixels > 0); } - SoftwareRendererClasses::ClipRegionBase::Ptr clip; - int xOffset, yOffset; - Font font; - FillType fillType; - Graphics::ResamplingQuality interpolationQuality; - -private: - void cloneClipIfMultiplyReferenced() + void generate (PixelRGB* dest, const int x, int numPixels) throw() { - if (clip->getReferenceCount() > 1) - clip = clip->clone(); - } + this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); - SavedState& operator= (const SavedState&); -}; + do + { + int hiResX, hiResY; + this->interpolator.next (hiResX, hiResY); + hiResX += pixelOffsetInt; + hiResY += pixelOffsetInt; + int loResX = hiResX >> 8; + int loResY = hiResY >> 8; -LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image_) - : image (image_) -{ - currentState = new SavedState (image_.getBounds(), 0, 0); -} + if (repeatPattern) + { + loResX = safeModulo (loResX, srcData.width); + loResY = safeModulo (loResY, srcData.height); + } -LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image_, const int xOffset, const int yOffset, - const RectangleList& initialClip) - : image (image_) -{ - currentState = new SavedState (initialClip, xOffset, yOffset); -} + if (betterQuality + && ((unsigned int) loResX) < (unsigned int) maxX + && ((unsigned int) loResY) < (unsigned int) maxY) + { + uint32 c[3] = { 256 * 128, 256 * 128, 256 * 128 }; + hiResX &= 255; + hiResY &= 255; -LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() -{ -} + const uint8* src = this->srcData.getPixelPointer (loResX, loResY); -bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const -{ - return false; -} + unsigned int weight = (256 - hiResX) * (256 - hiResY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; -void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) -{ - currentState->setOrigin (x, y); -} + weight = hiResX * (256 - hiResY); + c[0] += weight * src[3]; + c[1] += weight * src[4]; + c[2] += weight * src[5]; -bool LowLevelGraphicsSoftwareRenderer::clipToRectangle (const Rectangle& r) -{ - return currentState->clipToRectangle (r); -} + src += this->srcData.lineStride; -bool LowLevelGraphicsSoftwareRenderer::clipToRectangleList (const RectangleList& clipRegion) -{ - return currentState->clipToRectangleList (clipRegion); -} + weight = (256 - hiResX) * hiResY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; -void LowLevelGraphicsSoftwareRenderer::excludeClipRectangle (const Rectangle& r) -{ - currentState->excludeClipRectangle (r); -} + weight = hiResX * hiResY; + c[0] += weight * src[3]; + c[1] += weight * src[4]; + c[2] += weight * src[5]; -void LowLevelGraphicsSoftwareRenderer::clipToPath (const Path& path, const AffineTransform& transform) -{ - currentState->clipToPath (path, transform); -} + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 16), + (uint8) (c[PixelRGB::indexG] >> 16), + (uint8) (c[PixelRGB::indexB] >> 16)); + } + else + { + if (! repeatPattern) + { + // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable + if (loResX < 0) loResX = 0; + if (loResY < 0) loResY = 0; + if (loResX > maxX) loResX = maxX; + if (loResY > maxY) loResY = maxY; + } -void LowLevelGraphicsSoftwareRenderer::clipToImageAlpha (const Image& sourceImage, const Rectangle& srcClip, const AffineTransform& transform) -{ - currentState->clipToImageAlpha (sourceImage, srcClip, transform); -} + dest->set (*(const PixelRGB*) this->srcData.getPixelPointer (loResX, loResY)); + } -bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (const Rectangle& r) -{ - return currentState->clipRegionIntersects (r); -} + ++dest; -const Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const -{ - return currentState->getClipBounds(); -} + } while (--numPixels > 0); + } -bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const -{ - return currentState->clip == 0; -} + void generate (PixelAlpha* dest, const int x, int numPixels) throw() + { + this->interpolator.setStartOfLine (x + pixelOffset, y + pixelOffset, numPixels); -void LowLevelGraphicsSoftwareRenderer::saveState() -{ - stateStack.add (new SavedState (*currentState)); -} + do + { + int hiResX, hiResY; + this->interpolator.next (hiResX, hiResY); + hiResX += pixelOffsetInt; + hiResY += pixelOffsetInt; + int loResX = hiResX >> 8; + int loResY = hiResY >> 8; + + if (repeatPattern) + { + loResX = safeModulo (loResX, srcData.width); + loResY = safeModulo (loResY, srcData.height); + } -void LowLevelGraphicsSoftwareRenderer::restoreState() -{ - SavedState* const top = stateStack.getLast(); + if (betterQuality + && ((unsigned int) loResX) < (unsigned int) maxX + && ((unsigned int) loResY) < (unsigned int) maxY) + { + hiResX &= 255; + hiResY &= 255; - if (top != 0) - { - currentState = top; - stateStack.removeLast (1, false); - } - else - { - jassertfalse; // trying to pop with an empty stack! - } -} + const uint8* src = this->srcData.getPixelPointer (loResX, loResY); + uint32 c = 256 * 128; + c += src[0] * ((256 - hiResX) * (256 - hiResY)); + c += src[1] * (hiResX * (256 - hiResY)); + src += this->srcData.lineStride; + c += src[0] * ((256 - hiResX) * hiResY); + c += src[1] * (hiResX * hiResY); -void LowLevelGraphicsSoftwareRenderer::setFill (const FillType& fillType) -{ - currentState->fillType = fillType; -} + *((uint8*) dest) = (uint8) c; + } + else + { + if (! repeatPattern) + { + // Beyond the edges, just repeat the edge pixels and leave the anti-aliasing to be handled by the edgetable + if (loResX < 0) loResX = 0; + if (loResY < 0) loResY = 0; + if (loResX > maxX) loResX = maxX; + if (loResY > maxY) loResY = maxY; + } -void LowLevelGraphicsSoftwareRenderer::setOpacity (float newOpacity) -{ - currentState->fillType.setOpacity (newOpacity); -} + *((uint8*) dest) = *(this->srcData.getPixelPointer (loResX, loResY)); + } -void LowLevelGraphicsSoftwareRenderer::setInterpolationQuality (Graphics::ResamplingQuality quality) -{ - currentState->interpolationQuality = quality; -} + ++dest; -void LowLevelGraphicsSoftwareRenderer::fillRect (const Rectangle& r, const bool replaceExistingContents) -{ - currentState->fillRect (image, r, replaceExistingContents); -} + } while (--numPixels > 0); + } -void LowLevelGraphicsSoftwareRenderer::fillPath (const Path& path, const AffineTransform& transform) -{ - currentState->fillPath (image, path, transform); -} + class TransformedImageSpanInterpolator + { + public: + TransformedImageSpanInterpolator (const AffineTransform& transform) throw() + : inverseTransform (transform.inverted()) + {} -void LowLevelGraphicsSoftwareRenderer::drawImage (const Image& sourceImage, const Rectangle& srcClip, - const AffineTransform& transform, const bool fillEntireClipAsTiles) -{ - jassert (sourceImage.getBounds().contains (srcClip)); + void setStartOfLine (float x, float y, const int numPixels) throw() + { + float x1 = x, y1 = y; + x += numPixels; + inverseTransform.transformPoints (x1, y1, x, y); - currentState->renderImage (image, sourceImage, srcClip, transform, - fillEntireClipAsTiles ? currentState->clip : 0); -} + xBresenham.set ((int) (x1 * 256.0f), (int) (x * 256.0f), numPixels); + yBresenham.set ((int) (y1 * 256.0f), (int) (y * 256.0f), numPixels); + } -void LowLevelGraphicsSoftwareRenderer::drawLine (const Line & line) -{ - Path p; - p.addLineSegment (line, 1.0f); - fillPath (p, AffineTransform::identity); -} + void next (int& x, int& y) throw() + { + x = xBresenham.n; + xBresenham.stepToNext(); + y = yBresenham.n; + yBresenham.stepToNext(); + } -void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, float top, float bottom) -{ - if (bottom > top) - currentState->fillRect (image, Rectangle ((float) x, top, 1.0f, bottom - top)); -} + private: + class BresenhamInterpolator + { + public: + BresenhamInterpolator() throw() {} -void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, float left, float right) -{ - if (right > left) - currentState->fillRect (image, Rectangle (left, (float) y, right - left, 1.0f)); -} + void set (const int n1, const int n2, const int numSteps_) throw() + { + numSteps = jmax (1, numSteps_); + step = (n2 - n1) / numSteps; + remainder = modulo = (n2 - n1) % numSteps; + n = n1; -class LowLevelGraphicsSoftwareRenderer::CachedGlyph -{ -public: - CachedGlyph() : glyph (0), lastAccessCount (0) {} - ~CachedGlyph() {} + if (modulo <= 0) + { + modulo += numSteps; + remainder += numSteps; + --step; + } - void draw (SavedState& state, Image& image, const float x, const float y) const - { - if (edgeTable != 0) - state.fillEdgeTable (image, *edgeTable, x, roundToInt (y)); - } + modulo -= numSteps; + } - void generate (const Font& newFont, const int glyphNumber) - { - font = newFont; - glyph = glyphNumber; - edgeTable = 0; + forcedinline void stepToNext() throw() + { + modulo += remainder; + n += step; - Path glyphPath; - font.getTypeface()->getOutlineForGlyph (glyphNumber, glyphPath); + if (modulo > 0) + { + modulo -= numSteps; + ++n; + } + } - if (! glyphPath.isEmpty()) - { - const float fontHeight = font.getHeight(); - const AffineTransform transform (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) - .translated (0.0f, -0.5f)); + int n; - edgeTable = new EdgeTable (glyphPath.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), - glyphPath, transform); - } - } + private: + int numSteps, step, modulo, remainder; + }; - int glyph, lastAccessCount; - Font font; + const AffineTransform inverseTransform; + BresenhamInterpolator xBresenham, yBresenham; - juce_UseDebuggingNewOperator + TransformedImageSpanInterpolator (const TransformedImageSpanInterpolator&); + TransformedImageSpanInterpolator& operator= (const TransformedImageSpanInterpolator&); + }; -private: - ScopedPointer edgeTable; + TransformedImageSpanInterpolator interpolator; + const Image::BitmapData& destData; + const Image::BitmapData& srcData; + const int extraAlpha; + const bool betterQuality; + const float pixelOffset; + const int pixelOffsetInt, maxX, maxY; + int y; + DestPixelType* linePixels; + HeapBlock scratchBuffer; + int scratchSize; - CachedGlyph (const CachedGlyph&); - CachedGlyph& operator= (const CachedGlyph&); + TransformedImageFillEdgeTableRenderer (const TransformedImageFillEdgeTableRenderer&); + TransformedImageFillEdgeTableRenderer& operator= (const TransformedImageFillEdgeTableRenderer&); }; -class LowLevelGraphicsSoftwareRenderer::GlyphCache : private DeletedAtShutdown +class ClipRegionBase : public ReferenceCountedObject { public: - GlyphCache() - : accessCounter (0), hits (0), misses (0) - { - for (int i = 120; --i >= 0;) - glyphs.add (new CachedGlyph()); - } + ClipRegionBase() {} + virtual ~ClipRegionBase() {} - ~GlyphCache() - { - clearSingletonInstance(); - } + typedef ReferenceCountedObjectPtr Ptr; - juce_DeclareSingleton_SingleThreaded_Minimal (GlyphCache); + virtual const Ptr clone() const = 0; + virtual const Ptr applyClipTo (const Ptr& target) const = 0; - void drawGlyph (SavedState& state, Image& image, const Font& font, const int glyphNumber, float x, float y) - { - ++accessCounter; - int oldestCounter = std::numeric_limits::max(); - CachedGlyph* oldest = 0; + virtual const Ptr clipToRectangle (const Rectangle& r) = 0; + virtual const Ptr clipToRectangleList (const RectangleList& r) = 0; + virtual const Ptr excludeClipRectangle (const Rectangle& r) = 0; + virtual const Ptr clipToPath (const Path& p, const AffineTransform& transform) = 0; + virtual const Ptr clipToEdgeTable (const EdgeTable& et) = 0; + virtual const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& t, const bool betterQuality) = 0; - for (int i = glyphs.size(); --i >= 0;) + virtual bool clipRegionIntersects (const Rectangle& r) const = 0; + virtual const Rectangle getClipBounds() const = 0; + + virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const = 0; + virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const = 0; + virtual void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const = 0; + virtual void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const = 0; + virtual void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& t, bool betterQuality, bool tiledFill) const = 0; + virtual void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const = 0; + +protected: + + template + static void renderImageTransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, + const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) + { + switch (destData.pixelFormat) { - CachedGlyph* const glyph = glyphs.getUnchecked (i); + case Image::ARGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + } + break; - if (glyph->glyph == glyphNumber && glyph->font == font) + case Image::RGB: + switch (srcData.pixelFormat) { - ++hits; - glyph->lastAccessCount = accessCounter; - glyph->draw (state, image, x, y); - return; + case Image::ARGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; } + break; - if (glyph->lastAccessCount <= oldestCounter) + default: + switch (srcData.pixelFormat) { - oldestCounter = glyph->lastAccessCount; - oldest = glyph; + case Image::ARGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; } + break; } + } - if (hits + ++misses > (glyphs.size() << 4)) + template + static void renderImageUntransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) + { + switch (destData.pixelFormat) { - if (misses * 2 > hits) + case Image::ARGB: + switch (srcData.pixelFormat) { - for (int i = 32; --i >= 0;) - glyphs.add (new CachedGlyph()); + case Image::ARGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; } + break; - hits = misses = 0; - oldest = glyphs.getLast(); - } + case Image::RGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + } + break; - jassert (oldest != 0); - oldest->lastAccessCount = accessCounter; - oldest->generate (font, glyphNumber); - oldest->draw (state, image, x, y); + default: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + } + break; + } } - juce_UseDebuggingNewOperator + template + static void renderSolidFill (Iterator& iter, const Image::BitmapData& destData, const PixelARGB& fillColour, const bool replaceContents, DestPixelType*) + { + jassert (destData.pixelStride == sizeof (DestPixelType)); + if (replaceContents) + { + SolidColourEdgeTableRenderer r (destData, fillColour); + iter.iterate (r); + } + else + { + SolidColourEdgeTableRenderer r (destData, fillColour); + iter.iterate (r); + } + } -private: - friend class OwnedArray ; - OwnedArray glyphs; - int accessCounter, hits, misses; + template + static void renderGradient (Iterator& iter, const Image::BitmapData& destData, const ColourGradient& g, const AffineTransform& transform, + const PixelARGB* const lookupTable, const int numLookupEntries, const bool isIdentity, DestPixelType*) + { + jassert (destData.pixelStride == sizeof (DestPixelType)); - GlyphCache (const GlyphCache&); - GlyphCache& operator= (const GlyphCache&); + if (g.isRadial) + { + if (isIdentity) + { + GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + else + { + GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + } + else + { + GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + } }; -juce_ImplementSingleton_SingleThreaded (LowLevelGraphicsSoftwareRenderer::GlyphCache); - -void LowLevelGraphicsSoftwareRenderer::setFont (const Font& newFont) -{ - currentState->font = newFont; -} - -const Font LowLevelGraphicsSoftwareRenderer::getFont() +class ClipRegion_EdgeTable : public ClipRegionBase { - return currentState->font; -} +public: + ClipRegion_EdgeTable (const EdgeTable& e) : edgeTable (e) {} + ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} + ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} + ClipRegion_EdgeTable (const RectangleList& r) : edgeTable (r) {} + ClipRegion_EdgeTable (const Rectangle& bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} + ClipRegion_EdgeTable (const ClipRegion_EdgeTable& other) : edgeTable (other.edgeTable) {} + ~ClipRegion_EdgeTable() {} -void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) -{ - Font& f = currentState->font; + const Ptr clone() const + { + return new ClipRegion_EdgeTable (*this); + } - if (transform.isOnlyTranslation()) + const Ptr applyClipTo (const Ptr& target) const { - GlyphCache::getInstance()->drawGlyph (*currentState, image, f, glyphNumber, - transform.getTranslationX(), - transform.getTranslationY()); + return target->clipToEdgeTable (edgeTable); } - else + + const Ptr clipToRectangle (const Rectangle& r) { - Path p; - f.getTypeface()->getOutlineForGlyph (glyphNumber, p); - fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()).followedBy (transform)); + edgeTable.clipToRectangle (r); + return edgeTable.isEmpty() ? 0 : this; } -} -#if JUCE_MSVC - #pragma warning (pop) + const Ptr clipToRectangleList (const RectangleList& r) + { + RectangleList inverse (edgeTable.getMaximumBounds()); - #if JUCE_DEBUG - #pragma optimize ("", on) // resets optimisations to the project defaults - #endif -#endif + if (inverse.subtract (r)) + for (RectangleList::Iterator iter (inverse); iter.next();) + edgeTable.excludeRectangle (*iter.getRectangle()); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_LowLevelGraphicsSoftwareRenderer.cpp ***/ + return edgeTable.isEmpty() ? 0 : this; + } + const Ptr excludeClipRectangle (const Rectangle& r) + { + edgeTable.excludeRectangle (r); + return edgeTable.isEmpty() ? 0 : this; + } -/*** Start of inlined file: juce_RectanglePlacement.cpp ***/ -BEGIN_JUCE_NAMESPACE + const Ptr clipToPath (const Path& p, const AffineTransform& transform) + { + EdgeTable et (edgeTable.getMaximumBounds(), p, transform); + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? 0 : this; + } -RectanglePlacement::RectanglePlacement (const RectanglePlacement& other) throw() - : flags (other.flags) -{ -} + const Ptr clipToEdgeTable (const EdgeTable& et) + { + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? 0 : this; + } -RectanglePlacement& RectanglePlacement::operator= (const RectanglePlacement& other) throw() -{ - flags = other.flags; - return *this; -} + const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& transform, const bool betterQuality) + { + const Image::BitmapData srcData (image, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); -void RectanglePlacement::applyTo (double& x, double& y, - double& w, double& h, - const double dx, const double dy, - const double dw, const double dh) const throw() -{ - if (w == 0 || h == 0) - return; + if (transform.isOnlyTranslation()) + { + // If our translation doesn't involve any distortion, just use a simple blit.. + const int tx = (int) (transform.getTranslationX() * 256.0f); + const int ty = (int) (transform.getTranslationY() * 256.0f); + + if ((! betterQuality) || ((tx | ty) & 224) == 0) + { + const int imageX = ((tx + 128) >> 8); + const int imageY = ((ty + 128) >> 8); - if ((flags & stretchToFit) != 0) - { - x = dx; - y = dy; - w = dw; - h = dh; - } - else - { - double scale = (flags & fillDestination) != 0 ? jmax (dw / w, dh / h) - : jmin (dw / w, dh / h); + if (image.getFormat() == Image::ARGB) + straightClipImage (srcData, imageX, imageY, (PixelARGB*) 0); + else + straightClipImage (srcData, imageX, imageY, (PixelAlpha*) 0); - if ((flags & onlyReduceInSize) != 0) - scale = jmin (scale, 1.0); + return edgeTable.isEmpty() ? 0 : this; + } + } - if ((flags & onlyIncreaseInSize) != 0) - scale = jmax (scale, 1.0); + if (transform.isSingularity()) + return 0; - w *= scale; - h *= scale; + { + Path p; + p.addRectangle (0, 0, (float) srcData.width, (float) srcData.height); + EdgeTable et2 (edgeTable.getMaximumBounds(), p, transform); + edgeTable.clipToEdgeTable (et2); + } - if ((flags & xLeft) != 0) - x = dx; - else if ((flags & xRight) != 0) - x = dx + dw - w; - else - x = dx + (dw - w) * 0.5; + if (! edgeTable.isEmpty()) + { + if (image.getFormat() == Image::ARGB) + transformedClipImage (srcData, transform, betterQuality, (PixelARGB*) 0); + else + transformedClipImage (srcData, transform, betterQuality, (PixelAlpha*) 0); + } - if ((flags & yTop) != 0) - y = dy; - else if ((flags & yBottom) != 0) - y = dy + dh - h; - else - y = dy + (dh - h) * 0.5; + return edgeTable.isEmpty() ? 0 : this; } -} -const AffineTransform RectanglePlacement::getTransformToFit (float x, float y, - float w, float h, - const float dx, const float dy, - const float dw, const float dh) const throw() -{ - if (w == 0 || h == 0) - return AffineTransform::identity; + bool clipRegionIntersects (const Rectangle& r) const + { + return edgeTable.getMaximumBounds().intersects (r); + } - const float scaleX = dw / w; - const float scaleY = dh / h; + const Rectangle getClipBounds() const + { + return edgeTable.getMaximumBounds(); + } - if ((flags & stretchToFit) != 0) - return AffineTransform::translation (-x, -y) - .scaled (scaleX, scaleY) - .translated (dx, dy); + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds()); + const Rectangle clipped (totalClip.getIntersection (area)); - float scale = (flags & fillDestination) != 0 ? jmax (scaleX, scaleY) - : jmin (scaleX, scaleY); + if (! clipped.isEmpty()) + { + ClipRegion_EdgeTable et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + et.fillAllWithColour (destData, colour, replaceContents); + } + } - if ((flags & onlyReduceInSize) != 0) - scale = jmin (scale, 1.0f); + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds().toFloat()); + const Rectangle clipped (totalClip.getIntersection (area)); - if ((flags & onlyIncreaseInSize) != 0) - scale = jmax (scale, 1.0f); + if (! clipped.isEmpty()) + { + ClipRegion_EdgeTable et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + et.fillAllWithColour (destData, colour, false); + } + } - w *= scale; - h *= scale; + void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const + { + switch (destData.pixelFormat) + { + case Image::ARGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } - float newX = dx; + void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + HeapBlock lookupTable; + const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); + jassert (numLookupEntries > 0); - if ((flags & xRight) != 0) - newX += dw - w; // right - else if ((flags & xLeft) == 0) - newX += (dw - w) / 2.0f; // centre + switch (destData.pixelFormat) + { + case Image::ARGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; + case Image::RGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; + default: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + } + } - float newY = dy; + void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const + { + renderImageTransformedInternal (edgeTable, destData, srcData, alpha, transform, betterQuality, tiledFill); + } - if ((flags & yBottom) != 0) - newY += dh - h; // bottom - else if ((flags & yTop) == 0) - newY += (dh - h) / 2.0f; // centre + void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const + { + renderImageUntransformedInternal (edgeTable, destData, srcData, alpha, x, y, tiledFill); + } - return AffineTransform::translation (-x, -y) - .scaled (scale, scale) - .translated (newX, newY); -} + EdgeTable edgeTable; -END_JUCE_NAMESPACE -/*** End of inlined file: juce_RectanglePlacement.cpp ***/ +private: + template + void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const bool betterQuality, const SrcPixelType*) + { + TransformedImageFillEdgeTableRenderer renderer (srcData, srcData, transform, 255, betterQuality); -/*** Start of inlined file: juce_Drawable.cpp ***/ -BEGIN_JUCE_NAMESPACE + for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) + renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), + edgeTable.getMaximumBounds().getWidth()); + } -Drawable::RenderingContext::RenderingContext (Graphics& g_, - const AffineTransform& transform_, - const float opacity_) throw() - : g (g_), - transform (transform_), - opacity (opacity_) -{ -} + template + void straightClipImage (const Image::BitmapData& srcData, int imageX, int imageY, const SrcPixelType*) + { + Rectangle r (imageX, imageY, srcData.width, srcData.height); + edgeTable.clipToRectangle (r); -Drawable::Drawable() - : parent (0) -{ -} + ImageFillEdgeTableRenderer renderer (srcData, srcData, 255, imageX, imageY); -Drawable::~Drawable() -{ -} + for (int y = 0; y < r.getHeight(); ++y) + renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); + } -void Drawable::draw (Graphics& g, const float opacity, const AffineTransform& transform) const -{ - render (RenderingContext (g, transform, opacity)); -} + ClipRegion_EdgeTable& operator= (const ClipRegion_EdgeTable&); +}; -void Drawable::drawAt (Graphics& g, const float x, const float y, const float opacity) const +class ClipRegion_RectangleList : public ClipRegionBase { - draw (g, opacity, AffineTransform::translation (x, y)); -} +public: + ClipRegion_RectangleList (const Rectangle& r) : clip (r) {} + ClipRegion_RectangleList (const RectangleList& r) : clip (r) {} + ClipRegion_RectangleList (const ClipRegion_RectangleList& other) : clip (other.clip) {} + ~ClipRegion_RectangleList() {} -void Drawable::drawWithin (Graphics& g, - const int destX, - const int destY, - const int destW, - const int destH, - const RectanglePlacement& placement, - const float opacity) const -{ - if (destW > 0 && destH > 0) + const Ptr clone() const { - Rectangle bounds (getBounds()); + return new ClipRegion_RectangleList (*this); + } - draw (g, opacity, - placement.getTransformToFit (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), - (float) destX, (float) destY, - (float) destW, (float) destH)); + const Ptr applyClipTo (const Ptr& target) const + { + return target->clipToRectangleList (clip); } -} -Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes) -{ - Drawable* result = 0; + const Ptr clipToRectangle (const Rectangle& r) + { + clip.clipTo (r); + return clip.isEmpty() ? 0 : this; + } - Image image (ImageFileFormat::loadFrom (data, (int) numBytes)); + const Ptr clipToRectangleList (const RectangleList& r) + { + clip.clipTo (r); + return clip.isEmpty() ? 0 : this; + } - if (image.isValid()) + const Ptr excludeClipRectangle (const Rectangle& r) { - DrawableImage* const di = new DrawableImage(); - di->setImage (image); - result = di; + clip.subtract (r); + return clip.isEmpty() ? 0 : this; } - else + + const Ptr clipToPath (const Path& p, const AffineTransform& transform) { - const String asString (String::createStringFromData (data, (int) numBytes)); + return Ptr (new ClipRegion_EdgeTable (clip))->clipToPath (p, transform); + } - XmlDocument doc (asString); - ScopedPointer outer (doc.getDocumentElement (true)); + const Ptr clipToEdgeTable (const EdgeTable& et) + { + return Ptr (new ClipRegion_EdgeTable (clip))->clipToEdgeTable (et); + } - if (outer != 0 && outer->hasTagName ("svg")) - { - ScopedPointer svg (doc.getDocumentElement()); + const Ptr clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& transform, const bool betterQuality) + { + return Ptr (new ClipRegion_EdgeTable (clip))->clipToImageAlpha (image, srcClip, transform, betterQuality); + } - if (svg != 0) - result = Drawable::createFromSVG (*svg); - } + bool clipRegionIntersects (const Rectangle& r) const + { + return clip.intersects (r); } - return result; -} + const Rectangle getClipBounds() const + { + return clip.getBounds(); + } -Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) -{ - MemoryBlock mb; - dataSource.readIntoMemoryBlock (mb); + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + { + SubRectangleIterator iter (clip, area); - return createFromImageData (mb.getData(), mb.getSize()); -} + switch (destData.pixelFormat) + { + case Image::ARGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } -Drawable* Drawable::createFromImageFile (const File& file) -{ - const ScopedPointer fin (file.createInputStream()); + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const + { + SubRectangleIteratorFloat iter (clip, area); - return fin != 0 ? createFromImageDataStream (*fin) : 0; -} + switch (destData.pixelFormat) + { + case Image::ARGB: renderSolidFill (iter, destData, colour, false, (PixelARGB*) 0); break; + case Image::RGB: renderSolidFill (iter, destData, colour, false, (PixelRGB*) 0); break; + default: renderSolidFill (iter, destData, colour, false, (PixelAlpha*) 0); break; + } + } -Drawable* Drawable::createFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) -{ - return createChildFromValueTree (0, tree, imageProvider); -} + void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const + { + switch (destData.pixelFormat) + { + case Image::ARGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: renderSolidFill (*this, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } -Drawable* Drawable::createChildFromValueTree (DrawableComposite* parent, const ValueTree& tree, ImageProvider* imageProvider) -{ - const Identifier type (tree.getType()); + void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + HeapBlock lookupTable; + const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); + jassert (numLookupEntries > 0); - Drawable* d = 0; - if (type == DrawablePath::valueTreeType) - d = new DrawablePath(); - else if (type == DrawableComposite::valueTreeType) - d = new DrawableComposite(); - else if (type == DrawableImage::valueTreeType) - d = new DrawableImage(); - else if (type == DrawableText::valueTreeType) - d = new DrawableText(); + switch (destData.pixelFormat) + { + case Image::ARGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; + case Image::RGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; + default: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + } + } - if (d != 0) + void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const { - d->parent = parent; - d->refreshFromValueTree (tree, imageProvider); + renderImageTransformedInternal (*this, destData, srcData, alpha, transform, betterQuality, tiledFill); } - return d; -} - -const Identifier Drawable::ValueTreeWrapperBase::idProperty ("id"); -const Identifier Drawable::ValueTreeWrapperBase::type ("type"); -const Identifier Drawable::ValueTreeWrapperBase::gradientPoint1 ("point1"); -const Identifier Drawable::ValueTreeWrapperBase::gradientPoint2 ("point2"); -const Identifier Drawable::ValueTreeWrapperBase::gradientPoint3 ("point3"); -const Identifier Drawable::ValueTreeWrapperBase::colour ("colour"); -const Identifier Drawable::ValueTreeWrapperBase::radial ("radial"); -const Identifier Drawable::ValueTreeWrapperBase::colours ("colours"); -const Identifier Drawable::ValueTreeWrapperBase::imageId ("imageId"); -const Identifier Drawable::ValueTreeWrapperBase::imageOpacity ("imageOpacity"); + void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const + { + renderImageUntransformedInternal (*this, destData, srcData, alpha, x, y, tiledFill); + } -Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& state_) - : state (state_) -{ -} + RectangleList clip; -Drawable::ValueTreeWrapperBase::~ValueTreeWrapperBase() -{ -} + template + void iterate (Renderer& r) const throw() + { + RectangleList::Iterator iter (clip); -const String Drawable::ValueTreeWrapperBase::getID() const -{ - return state [idProperty]; -} + while (iter.next()) + { + const Rectangle rect (*iter.getRectangle()); + const int x = rect.getX(); + const int w = rect.getWidth(); + jassert (w > 0); + const int bottom = rect.getBottom(); -void Drawable::ValueTreeWrapperBase::setID (const String& newID, UndoManager* const undoManager) -{ - if (newID.isEmpty()) - state.removeProperty (idProperty, undoManager); - else - state.setProperty (idProperty, newID, undoManager); -} + for (int y = rect.getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } -const FillType Drawable::ValueTreeWrapperBase::readFillType (const ValueTree& v, RelativePoint* const gp1, RelativePoint* const gp2, RelativePoint* const gp3, - RelativeCoordinate::NamedCoordinateFinder* const nameFinder, ImageProvider* imageProvider) -{ - const String newType (v[type].toString()); +private: - if (newType == "solid") - { - const String colourString (v [colour].toString()); - return FillType (Colour (colourString.isEmpty() ? (uint32) 0xff000000 - : (uint32) colourString.getHexValue32())); - } - else if (newType == "gradient") + class SubRectangleIterator { - RelativePoint p1 (v [gradientPoint1]), p2 (v [gradientPoint2]), p3 (v [gradientPoint3]); + public: + SubRectangleIterator (const RectangleList& clip_, const Rectangle& area_) + : clip (clip_), area (area_) + { + } - ColourGradient g; + template + void iterate (Renderer& r) const throw() + { + RectangleList::Iterator iter (clip); - if (gp1 != 0) *gp1 = p1; - if (gp2 != 0) *gp2 = p2; - if (gp3 != 0) *gp3 = p3; + while (iter.next()) + { + const Rectangle rect (iter.getRectangle()->getIntersection (area)); - g.point1 = p1.resolve (nameFinder); - g.point2 = p2.resolve (nameFinder); - g.isRadial = v[radial]; + if (! rect.isEmpty()) + { + const int x = rect.getX(); + const int w = rect.getWidth(); + const int bottom = rect.getBottom(); - StringArray colourSteps; - colourSteps.addTokens (v[colours].toString(), false); + for (int y = rect.getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } + } - for (int i = 0; i < colourSteps.size() / 2; ++i) - g.addColour (colourSteps[i * 2].getDoubleValue(), - Colour ((uint32) colourSteps[i * 2 + 1].getHexValue32())); + private: + const RectangleList& clip; + const Rectangle area; - FillType fillType (g); + SubRectangleIterator (const SubRectangleIterator&); + SubRectangleIterator& operator= (const SubRectangleIterator&); + }; - if (g.isRadial) + class SubRectangleIteratorFloat + { + public: + SubRectangleIteratorFloat (const RectangleList& clip_, const Rectangle& area_) + : clip (clip_), area (area_) { - const Point point3 (p3.resolve (nameFinder)); - const Point point3Source (g.point1.getX() + g.point2.getY() - g.point1.getY(), - g.point1.getY() + g.point1.getX() - g.point2.getX()); - - fillType.transform = AffineTransform::fromTargetPoints (g.point1.getX(), g.point1.getY(), g.point1.getX(), g.point1.getY(), - g.point2.getX(), g.point2.getY(), g.point2.getX(), g.point2.getY(), - point3Source.getX(), point3Source.getY(), point3.getX(), point3.getY()); } - return fillType; - } - else if (newType == "image") - { - Image im; - if (imageProvider != 0) - im = imageProvider->getImageForIdentifier (v[imageId]); + template + void iterate (Renderer& r) const throw() + { + int left = roundToInt (area.getX() * 256.0f); + int top = roundToInt (area.getY() * 256.0f); + int right = roundToInt (area.getRight() * 256.0f); + int bottom = roundToInt (area.getBottom() * 256.0f); - FillType f (im, AffineTransform::identity); - f.setOpacity ((float) v.getProperty (imageOpacity, 1.0f)); - return f; - } + int totalTop, totalLeft, totalBottom, totalRight; + int topAlpha, leftAlpha, bottomAlpha, rightAlpha; - jassertfalse; - return FillType(); -} + if ((top >> 8) == (bottom >> 8)) + { + topAlpha = bottom - top; + bottomAlpha = 0; + totalTop = top >> 8; + totalBottom = bottom = top = totalTop + 1; + } + else + { + if ((top & 255) == 0) + { + topAlpha = 0; + top = totalTop = (top >> 8); + } + else + { + topAlpha = 255 - (top & 255); + totalTop = (top >> 8); + top = totalTop + 1; + } -static const Point calcThirdGradientPoint (const FillType& fillType) -{ - const ColourGradient& g = *fillType.gradient; - const Point point3Source (g.point1.getX() + g.point2.getY() - g.point1.getY(), - g.point1.getY() + g.point1.getX() - g.point2.getX()); + bottomAlpha = bottom & 255; + bottom >>= 8; + totalBottom = bottom + (bottomAlpha != 0 ? 1 : 0); + } - return point3Source.transformedBy (fillType.transform); -} + if ((left >> 8) == (right >> 8)) + { + leftAlpha = right - left; + rightAlpha = 0; + totalLeft = (left >> 8); + totalRight = right = left = totalLeft + 1; + } + else + { + if ((left & 255) == 0) + { + leftAlpha = 0; + left = totalLeft = (left >> 8); + } + else + { + leftAlpha = 255 - (left & 255); + totalLeft = (left >> 8); + left = totalLeft + 1; + } -void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType& fillType, - const RelativePoint* const gp1, const RelativePoint* const gp2, const RelativePoint* gp3, - ImageProvider* imageProvider, UndoManager* const undoManager) -{ - if (fillType.isColour()) - { - v.setProperty (type, "solid", undoManager); - v.setProperty (colour, String::toHexString ((int) fillType.colour.getARGB()), undoManager); - } - else if (fillType.isGradient()) - { - v.setProperty (type, "gradient", undoManager); - v.setProperty (gradientPoint1, gp1 != 0 ? gp1->toString() : fillType.gradient->point1.toString(), undoManager); - v.setProperty (gradientPoint2, gp2 != 0 ? gp2->toString() : fillType.gradient->point2.toString(), undoManager); - v.setProperty (gradientPoint3, gp3 != 0 ? gp3->toString() : calcThirdGradientPoint (fillType).toString(), undoManager); + rightAlpha = right & 255; + right >>= 8; + totalRight = right + (rightAlpha != 0 ? 1 : 0); + } - v.setProperty (radial, fillType.gradient->isRadial, undoManager); + RectangleList::Iterator iter (clip); - String s; - for (int i = 0; i < fillType.gradient->getNumColours(); ++i) - s << ' ' << fillType.gradient->getColourPosition (i) - << ' ' << String::toHexString ((int) fillType.gradient->getColour(i).getARGB()); + while (iter.next()) + { + const int clipLeft = iter.getRectangle()->getX(); + const int clipRight = iter.getRectangle()->getRight(); + const int clipTop = iter.getRectangle()->getY(); + const int clipBottom = iter.getRectangle()->getBottom(); - v.setProperty (colours, s.trimStart(), undoManager); - } - else if (fillType.isTiledImage()) - { - v.setProperty (type, "image", undoManager); + if (totalBottom > clipTop && totalTop < clipBottom && totalRight > clipLeft && totalLeft < clipRight) + { + if (right - left == 1 && leftAlpha + rightAlpha == 0) // special case for 1-pix vertical lines + { + if (topAlpha != 0 && totalTop >= clipTop) + { + r.setEdgeTableYPos (totalTop); + r.handleEdgeTablePixel (left, topAlpha); + } - if (imageProvider != 0) - v.setProperty (imageId, imageProvider->getIdentifierForImage (fillType.image), undoManager); + const int endY = jmin (bottom, clipBottom); + for (int y = jmax (clipTop, top); y < endY; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTablePixelFull (left); + } - if (fillType.getOpacity() < 1.0f) - v.setProperty (imageOpacity, fillType.getOpacity(), undoManager); - else - v.removeProperty (imageOpacity, undoManager); - } - else - { - jassertfalse; - } -} + if (bottomAlpha != 0 && bottom < clipBottom) + { + r.setEdgeTableYPos (bottom); + r.handleEdgeTablePixel (left, bottomAlpha); + } + } + else + { + const int clippedLeft = jmax (left, clipLeft); + const int clippedWidth = jmin (right, clipRight) - clippedLeft; + const bool doLeftAlpha = leftAlpha != 0 && totalLeft >= clipLeft; + const bool doRightAlpha = rightAlpha != 0 && right < clipRight; -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Drawable.cpp ***/ + if (topAlpha != 0 && totalTop >= clipTop) + { + r.setEdgeTableYPos (totalTop); + if (doLeftAlpha) + r.handleEdgeTablePixel (totalLeft, (leftAlpha * topAlpha) >> 8); -/*** Start of inlined file: juce_DrawableComposite.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (clippedWidth > 0) + r.handleEdgeTableLine (clippedLeft, clippedWidth, topAlpha); -DrawableComposite::DrawableComposite() - : bounds (Point(), Point (100.0f, 0.0f), Point (0.0f, 100.0f)) -{ - setContentArea (RelativeRectangle (RelativeCoordinate (0.0), - RelativeCoordinate (100.0), - RelativeCoordinate (0.0), - RelativeCoordinate (100.0))); -} + if (doRightAlpha) + r.handleEdgeTablePixel (right, (rightAlpha * topAlpha) >> 8); + } -DrawableComposite::DrawableComposite (const DrawableComposite& other) -{ - bounds = other.bounds; + const int endY = jmin (bottom, clipBottom); + for (int y = jmax (clipTop, top); y < endY; ++y) + { + r.setEdgeTableYPos (y); - for (int i = 0; i < other.drawables.size(); ++i) - drawables.add (other.drawables.getUnchecked(i)->createCopy()); + if (doLeftAlpha) + r.handleEdgeTablePixel (totalLeft, leftAlpha); - markersX.addCopiesOf (other.markersX); - markersY.addCopiesOf (other.markersY); -} + if (clippedWidth > 0) + r.handleEdgeTableLineFull (clippedLeft, clippedWidth); -DrawableComposite::~DrawableComposite() -{ -} + if (doRightAlpha) + r.handleEdgeTablePixel (right, rightAlpha); + } -void DrawableComposite::insertDrawable (Drawable* drawable, const int index) -{ - if (drawable != 0) - { - jassert (! drawables.contains (drawable)); // trying to add a drawable that's already in here! - jassert (drawable->parent == 0); // A drawable can only live inside one parent at a time! - drawables.insert (index, drawable); - drawable->parent = this; - } -} + if (bottomAlpha != 0 && bottom < clipBottom) + { + r.setEdgeTableYPos (bottom); -void DrawableComposite::insertDrawable (const Drawable& drawable, const int index) -{ - insertDrawable (drawable.createCopy(), index); -} + if (doLeftAlpha) + r.handleEdgeTablePixel (totalLeft, (leftAlpha * bottomAlpha) >> 8); -void DrawableComposite::removeDrawable (const int index, const bool deleteDrawable) -{ - drawables.remove (index, deleteDrawable); -} + if (clippedWidth > 0) + r.handleEdgeTableLine (clippedLeft, clippedWidth, bottomAlpha); -Drawable* DrawableComposite::getDrawableWithName (const String& name) const throw() -{ - for (int i = drawables.size(); --i >= 0;) - if (drawables.getUnchecked(i)->getName() == name) - return drawables.getUnchecked(i); + if (doRightAlpha) + r.handleEdgeTablePixel (right, (rightAlpha * bottomAlpha) >> 8); + } + } + } + } + } - return 0; -} + private: + const RectangleList& clip; + const Rectangle& area; -void DrawableComposite::bringToFront (const int index) -{ - if (index >= 0 && index < drawables.size() - 1) - drawables.move (index, -1); -} + SubRectangleIteratorFloat (const SubRectangleIteratorFloat&); + SubRectangleIteratorFloat& operator= (const SubRectangleIteratorFloat&); + }; -void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBoundingBox) -{ - bounds = newBoundingBox; -} + ClipRegion_RectangleList& operator= (const ClipRegion_RectangleList&); +}; -DrawableComposite::Marker::Marker (const DrawableComposite::Marker& other) - : name (other.name), position (other.position) -{ } -DrawableComposite::Marker::Marker (const String& name_, const RelativeCoordinate& position_) - : name (name_), position (position_) +class LowLevelGraphicsSoftwareRenderer::SavedState { -} +public: + SavedState (const Rectangle& clip_, const int xOffset_, const int yOffset_) + : clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), + xOffset (xOffset_), yOffset (yOffset_), interpolationQuality (Graphics::mediumResamplingQuality) + { + } -bool DrawableComposite::Marker::operator!= (const DrawableComposite::Marker& other) const throw() -{ - return name != other.name || position != other.position; -} + SavedState (const RectangleList& clip_, const int xOffset_, const int yOffset_) + : clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), + xOffset (xOffset_), yOffset (yOffset_), interpolationQuality (Graphics::mediumResamplingQuality) + { + } -const char* const DrawableComposite::contentLeftMarkerName ("left"); -const char* const DrawableComposite::contentRightMarkerName ("right"); -const char* const DrawableComposite::contentTopMarkerName ("top"); -const char* const DrawableComposite::contentBottomMarkerName ("bottom"); + SavedState (const SavedState& other) + : clip (other.clip), xOffset (other.xOffset), yOffset (other.yOffset), font (other.font), + fillType (other.fillType), interpolationQuality (other.interpolationQuality) + { + } -const RelativeRectangle DrawableComposite::getContentArea() const -{ - jassert (markersX.size() >= 2 && getMarker (true, 0)->name == contentLeftMarkerName && getMarker (true, 1)->name == contentRightMarkerName); - jassert (markersY.size() >= 2 && getMarker (false, 0)->name == contentTopMarkerName && getMarker (false, 1)->name == contentBottomMarkerName); + ~SavedState() + { + } - return RelativeRectangle (markersX.getUnchecked(0)->position, markersX.getUnchecked(1)->position, - markersY.getUnchecked(0)->position, markersY.getUnchecked(1)->position); -} + void setOrigin (const int x, const int y) throw() + { + xOffset += x; + yOffset += y; + } -void DrawableComposite::setContentArea (const RelativeRectangle& newArea) -{ - setMarker (contentLeftMarkerName, true, newArea.left); - setMarker (contentRightMarkerName, true, newArea.right); - setMarker (contentTopMarkerName, false, newArea.top); - setMarker (contentBottomMarkerName, false, newArea.bottom); -} + bool clipToRectangle (const Rectangle& r) + { + if (clip != 0) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToRectangle (r.translated (xOffset, yOffset)); + } -void DrawableComposite::resetBoundingBoxToContentArea() -{ - const RelativeRectangle content (getContentArea()); + return clip != 0; + } - setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), - RelativePoint (content.right, content.top), - RelativePoint (content.left, content.bottom))); -} + bool clipToRectangleList (const RectangleList& r) + { + if (clip != 0) + { + cloneClipIfMultiplyReferenced(); -void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() -{ - const Rectangle bounds (getUntransformedBounds (false)); + RectangleList offsetList (r); + offsetList.offsetAll (xOffset, yOffset); + clip = clip->clipToRectangleList (offsetList); + } - setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX()), - RelativeCoordinate (bounds.getRight()), - RelativeCoordinate (bounds.getY()), - RelativeCoordinate (bounds.getBottom()))); - resetBoundingBoxToContentArea(); -} + return clip != 0; + } -int DrawableComposite::getNumMarkers (const bool xAxis) const throw() -{ - return (xAxis ? markersX : markersY).size(); -} + bool excludeClipRectangle (const Rectangle& r) + { + if (clip != 0) + { + cloneClipIfMultiplyReferenced(); + clip = clip->excludeClipRectangle (r.translated (xOffset, yOffset)); + } -const DrawableComposite::Marker* DrawableComposite::getMarker (const bool xAxis, const int index) const throw() -{ - return (xAxis ? markersX : markersY) [index]; -} + return clip != 0; + } -void DrawableComposite::setMarker (const String& name, const bool xAxis, const RelativeCoordinate& position) -{ - OwnedArray & markers = (xAxis ? markersX : markersY); + void clipToPath (const Path& p, const AffineTransform& transform) + { + if (clip != 0) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToPath (p, transform.translated ((float) xOffset, (float) yOffset)); + } + } - for (int i = 0; i < markers.size(); ++i) + void clipToImageAlpha (const Image& image, const Rectangle& srcClip, const AffineTransform& t) { - Marker* const m = markers.getUnchecked(i); - if (m->name == name) + if (clip != 0) { - if (m->position != position) + if (image.hasAlphaChannel()) { - m->position = position; - invalidatePoints(); + cloneClipIfMultiplyReferenced(); + clip = clip->clipToImageAlpha (image, srcClip, t.translated ((float) xOffset, (float) yOffset), + interpolationQuality != Graphics::lowResamplingQuality); + } + else + { + Path p; + p.addRectangle (srcClip); + clipToPath (p, t); } - - return; } } - (xAxis ? markersX : markersY).add (new Marker (name, position)); - invalidatePoints(); -} - -void DrawableComposite::removeMarker (const bool xAxis, const int index) -{ - jassert (index >= 2); - - if (index >= 2) - (xAxis ? markersX : markersY).remove (index); -} - -const AffineTransform DrawableComposite::calculateTransform() const -{ - Point resolved[3]; - bounds.resolveThreePoints (resolved, parent); - - const Rectangle content (getContentArea().resolve (parent)); - - return AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].getX(), resolved[0].getY(), - content.getRight(), content.getY(), resolved[1].getX(), resolved[1].getY(), - content.getX(), content.getBottom(), resolved[2].getX(), resolved[2].getY()); -} + bool clipRegionIntersects (const Rectangle& r) const + { + return clip != 0 && clip->clipRegionIntersects (r.translated (xOffset, yOffset)); + } -void DrawableComposite::render (const Drawable::RenderingContext& context) const -{ - if (drawables.size() > 0 && context.opacity > 0) + const Rectangle getClipBounds() const { - if (context.opacity >= 1.0f || drawables.size() == 1) - { - Drawable::RenderingContext contextCopy (context); - contextCopy.transform = calculateTransform().followedBy (context.transform); + return clip == 0 ? Rectangle() : clip->getClipBounds().translated (-xOffset, -yOffset); + } - for (int i = 0; i < drawables.size(); ++i) - drawables.getUnchecked(i)->render (contextCopy); - } - else + void fillRect (Image& image, const Rectangle& r, const bool replaceContents) + { + if (clip != 0) { - // To correctly render a whole composite layer with an overall transparency, - // we need to render everything opaquely into a temp buffer, then blend that - // with the target opacity... - const Rectangle clipBounds (context.g.getClipBounds()); - Image tempImage (Image::ARGB, clipBounds.getWidth(), clipBounds.getHeight(), true); - + if (fillType.isColour()) { - Graphics tempG (tempImage); - tempG.setOrigin (-clipBounds.getX(), -clipBounds.getY()); - Drawable::RenderingContext tempContext (tempG, context.transform, 1.0f); - render (tempContext); + Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); + clip->fillRectWithColour (destData, r.translated (xOffset, yOffset), fillType.colour.getPixelARGB(), replaceContents); } + else + { + const Rectangle totalClip (clip->getClipBounds()); + const Rectangle clipped (totalClip.getIntersection (r.translated (xOffset, yOffset))); - context.g.setOpacity (context.opacity); - context.g.drawImageAt (tempImage, clipBounds.getX(), clipBounds.getY()); + if (! clipped.isEmpty()) + fillShape (image, new SoftwareRendererClasses::ClipRegion_RectangleList (clipped), false); + } } } -} -const RelativeCoordinate DrawableComposite::findNamedCoordinate (const String& objectName, const String& edge) const -{ - if (objectName == RelativeCoordinate::Strings::parent) + void fillRect (Image& image, const Rectangle& r) { - if (edge == RelativeCoordinate::Strings::right || edge == RelativeCoordinate::Strings::bottom) + if (clip != 0) { - jassertfalse; // a Drawable doesn't have a fixed right-hand or bottom edge - use a marker instead if you need a point of reference. - return RelativeCoordinate (100.0); + if (fillType.isColour()) + { + Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); + clip->fillRectWithColour (destData, r.translated ((float) xOffset, (float) yOffset), fillType.colour.getPixelARGB()); + } + else + { + const Rectangle totalClip (clip->getClipBounds().toFloat()); + const Rectangle clipped (totalClip.getIntersection (r.translated ((float) xOffset, (float) yOffset))); + + if (! clipped.isEmpty()) + fillShape (image, new SoftwareRendererClasses::ClipRegion_EdgeTable (clipped), false); + } } } - int i; - for (i = 0; i < markersX.size(); ++i) + void fillPath (Image& image, const Path& path, const AffineTransform& transform) { - Marker* const m = markersX.getUnchecked(i); - if (m->name == objectName) - return m->position; + if (clip != 0) + fillShape (image, new SoftwareRendererClasses::ClipRegion_EdgeTable (clip->getClipBounds(), path, transform.translated ((float) xOffset, (float) yOffset)), false); } - for (i = 0; i < markersY.size(); ++i) + void fillEdgeTable (Image& image, const EdgeTable& edgeTable, const float x, const int y) { - Marker* const m = markersY.getUnchecked(i); - if (m->name == objectName) - return m->position; + if (clip != 0) + { + SoftwareRendererClasses::ClipRegion_EdgeTable* edgeTableClip = new SoftwareRendererClasses::ClipRegion_EdgeTable (edgeTable); + SoftwareRendererClasses::ClipRegionBase::Ptr shapeToFill (edgeTableClip); + edgeTableClip->edgeTable.translate (x + xOffset, y + yOffset); + fillShape (image, shapeToFill, false); + } } - return RelativeCoordinate(); -} + void fillShape (Image& image, SoftwareRendererClasses::ClipRegionBase::Ptr shapeToFill, const bool replaceContents) + { + jassert (clip != 0); -const Rectangle DrawableComposite::getUntransformedBounds (const bool includeMarkers) const -{ - Rectangle bounds; + shapeToFill = clip->applyClipTo (shapeToFill); - int i; - for (i = 0; i < drawables.size(); ++i) - bounds = bounds.getUnion (drawables.getUnchecked(i)->getBounds()); + if (shapeToFill != 0) + { + Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), true); - if (includeMarkers) + if (fillType.isGradient()) + { + jassert (! replaceContents); // that option is just for solid colours + + ColourGradient g2 (*(fillType.gradient)); + g2.multiplyOpacity (fillType.getOpacity()); + g2.point1.addXY (-0.5f, -0.5f); + g2.point2.addXY (-0.5f, -0.5f); + AffineTransform transform (fillType.transform.translated ((float) xOffset, (float) yOffset)); + const bool isIdentity = transform.isOnlyTranslation(); + + if (isIdentity) + { + // If our translation doesn't involve any distortion, we can speed it up.. + g2.point1.applyTransform (transform); + g2.point2.applyTransform (transform); + transform = AffineTransform::identity; + } + + shapeToFill->fillAllWithGradient (destData, g2, transform, isIdentity); + } + else if (fillType.isTiledImage()) + { + renderImage (image, fillType.image, fillType.image.getBounds(), fillType.transform, shapeToFill); + } + else + { + shapeToFill->fillAllWithColour (destData, fillType.colour.getPixelARGB(), replaceContents); + } + } + } + + void renderImage (Image& destImage, const Image& sourceImage, const Rectangle& srcClip, + const AffineTransform& t, const SoftwareRendererClasses::ClipRegionBase* const tiledFillClipRegion) { - if (markersX.size() > 0) + const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset)); + + const Image::BitmapData destData (destImage, 0, 0, destImage.getWidth(), destImage.getHeight(), true); + const Image::BitmapData srcData (sourceImage, srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); + const int alpha = fillType.colour.getAlpha(); + const bool betterQuality = (interpolationQuality != Graphics::lowResamplingQuality); + + if (transform.isOnlyTranslation()) { - float minX = std::numeric_limits::max(); - float maxX = std::numeric_limits::min(); + // If our translation doesn't involve any distortion, just use a simple blit.. + int tx = (int) (transform.getTranslationX() * 256.0f); + int ty = (int) (transform.getTranslationY() * 256.0f); - for (i = markersX.size(); --i >= 0;) + if ((! betterQuality) || ((tx | ty) & 224) == 0) { - const Marker* m = markersX.getUnchecked(i); - const float pos = (float) m->position.resolve (this); - minX = jmin (minX, pos); - maxX = jmax (maxX, pos); - } + tx = ((tx + 128) >> 8); + ty = ((ty + 128) >> 8); - if (minX <= maxX) - { - if (bounds.getHeight() > 0) + if (tiledFillClipRegion != 0) { - minX = jmin (minX, bounds.getX()); - maxX = jmax (maxX, bounds.getRight()); + tiledFillClipRegion->renderImageUntransformed (destData, srcData, alpha, tx, ty, true); + } + else + { + SoftwareRendererClasses::ClipRegionBase::Ptr c (new SoftwareRendererClasses::ClipRegion_EdgeTable (Rectangle (tx, ty, srcClip.getWidth(), srcClip.getHeight()).getIntersection (destImage.getBounds()))); + c = clip->applyClipTo (c); + + if (c != 0) + c->renderImageUntransformed (destData, srcData, alpha, tx, ty, false); } - bounds.setLeft (minX); - bounds.setWidth (maxX - minX); + return; } } - if (markersY.size() > 0) - { - float minY = std::numeric_limits::max(); - float maxY = std::numeric_limits::min(); + if (transform.isSingularity()) + return; - for (i = markersY.size(); --i >= 0;) - { - const Marker* m = markersY.getUnchecked(i); - const float pos = (float) m->position.resolve (this); - minY = jmin (minY, pos); - maxY = jmax (maxY, pos); - } + if (tiledFillClipRegion != 0) + { + tiledFillClipRegion->renderImageTransformed (destData, srcData, alpha, transform, betterQuality, true); + } + else + { + Path p; + p.addRectangle (srcClip); - if (minY <= maxY) - { - if (bounds.getHeight() > 0) - { - minY = jmin (minY, bounds.getY()); - maxY = jmax (maxY, bounds.getBottom()); - } + SoftwareRendererClasses::ClipRegionBase::Ptr c (clip->clone()); + c = c->clipToPath (p, transform); - bounds.setTop (minY); - bounds.setHeight (maxY - minY); - } + if (c != 0) + c->renderImageTransformed (destData, srcData, alpha, transform, betterQuality, false); } } - return bounds; -} - -const Rectangle DrawableComposite::getBounds() const -{ - return getUntransformedBounds (true).transformed (calculateTransform()); -} - -bool DrawableComposite::hitTest (float x, float y) const -{ - calculateTransform().inverted().transformPoint (x, y); + SoftwareRendererClasses::ClipRegionBase::Ptr clip; + int xOffset, yOffset; + Font font; + FillType fillType; + Graphics::ResamplingQuality interpolationQuality; - for (int i = 0; i < drawables.size(); ++i) - if (drawables.getUnchecked(i)->hitTest (x, y)) - return true; +private: + void cloneClipIfMultiplyReferenced() + { + if (clip->getReferenceCount() > 1) + clip = clip->clone(); + } - return false; -} + SavedState& operator= (const SavedState&); +}; -Drawable* DrawableComposite::createCopy() const +LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image_) + : image (image_) { - return new DrawableComposite (*this); + currentState = new SavedState (image_.getBounds(), 0, 0); } -void DrawableComposite::invalidatePoints() +LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image_, const int xOffset, const int yOffset, + const RectangleList& initialClip) + : image (image_) { - for (int i = 0; i < drawables.size(); ++i) - drawables.getUnchecked(i)->invalidatePoints(); + currentState = new SavedState (initialClip, xOffset, yOffset); } -const Identifier DrawableComposite::valueTreeType ("Group"); - -const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft"); -const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight"); -const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft"); -const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables"); -const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX"); -const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY"); -const Identifier DrawableComposite::ValueTreeWrapper::markerTag ("Marker"); -const Identifier DrawableComposite::ValueTreeWrapper::nameProperty ("name"); -const Identifier DrawableComposite::ValueTreeWrapper::posProperty ("position"); - -DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) - : ValueTreeWrapperBase (state_) +LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() { - jassert (state.hasType (valueTreeType)); } -ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const +bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const { - return state.getChildWithName (childGroupTag); + return false; } -ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) { - return state.getOrCreateChildWithName (childGroupTag, undoManager); + currentState->setOrigin (x, y); } -int DrawableComposite::ValueTreeWrapper::getNumDrawables() const +bool LowLevelGraphicsSoftwareRenderer::clipToRectangle (const Rectangle& r) { - return getChildList().getNumChildren(); + return currentState->clipToRectangle (r); } -ValueTree DrawableComposite::ValueTreeWrapper::getDrawableState (int index) const +bool LowLevelGraphicsSoftwareRenderer::clipToRectangleList (const RectangleList& clipRegion) { - return getChildList().getChild (index); + return currentState->clipToRectangleList (clipRegion); } -ValueTree DrawableComposite::ValueTreeWrapper::getDrawableWithId (const String& objectId, bool recursive) const +void LowLevelGraphicsSoftwareRenderer::excludeClipRectangle (const Rectangle& r) { - if (getID() == objectId) - return state; - - if (! recursive) - { - return getChildList().getChildWithProperty (idProperty, objectId); - } - else - { - const ValueTree childList (getChildList()); - - for (int i = getNumDrawables(); --i >= 0;) - { - const ValueTree& child = childList.getChild (i); - - if (child [Drawable::ValueTreeWrapperBase::idProperty] == objectId) - return child; - - if (child.hasType (DrawableComposite::valueTreeType)) - { - ValueTree v (DrawableComposite::ValueTreeWrapper (child).getDrawableWithId (objectId, true)); - - if (v.isValid()) - return v; - } - } - - return ValueTree::invalid; - } + currentState->excludeClipRectangle (r); } -int DrawableComposite::ValueTreeWrapper::indexOfDrawable (const ValueTree& item) const +void LowLevelGraphicsSoftwareRenderer::clipToPath (const Path& path, const AffineTransform& transform) { - return getChildList().indexOf (item); + currentState->clipToPath (path, transform); } -void DrawableComposite::ValueTreeWrapper::addDrawable (const ValueTree& newDrawableState, int index, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::clipToImageAlpha (const Image& sourceImage, const Rectangle& srcClip, const AffineTransform& transform) { - getChildListCreating (undoManager).addChild (newDrawableState, index, undoManager); + currentState->clipToImageAlpha (sourceImage, srcClip, transform); } -void DrawableComposite::ValueTreeWrapper::moveDrawableOrder (int currentIndex, int newIndex, UndoManager* undoManager) +bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (const Rectangle& r) { - getChildListCreating (undoManager).moveChild (currentIndex, newIndex, undoManager); + return currentState->clipRegionIntersects (r); } -void DrawableComposite::ValueTreeWrapper::removeDrawable (const ValueTree& child, UndoManager* undoManager) +const Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const { - getChildList().removeChild (child, undoManager); + return currentState->getClipBounds(); } -const RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const +bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const { - return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), - state.getProperty (topRight, "100, 0"), - state.getProperty (bottomLeft, "0, 100")); + return currentState->clip == 0; } -void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::saveState() { - state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); - state.setProperty (topRight, newBounds.topRight.toString(), undoManager); - state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); + stateStack.add (new SavedState (*currentState)); } -void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::restoreState() { - const RelativeRectangle content (getContentArea()); - - setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), - RelativePoint (content.right, content.top), - RelativePoint (content.left, content.bottom)), undoManager); -} + SavedState* const top = stateStack.getLast(); -const RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const -{ - return RelativeRectangle (getMarker (true, getMarkerState (true, 0)).position, - getMarker (true, getMarkerState (true, 1)).position, - getMarker (false, getMarkerState (false, 0)).position, - getMarker (false, getMarkerState (false, 1)).position); + if (top != 0) + { + currentState = top; + stateStack.removeLast (1, false); + } + else + { + jassertfalse; // trying to pop with an empty stack! + } } -void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::setFill (const FillType& fillType) { - setMarker (true, Marker (contentLeftMarkerName, newArea.left), undoManager); - setMarker (true, Marker (contentRightMarkerName, newArea.right), undoManager); - setMarker (false, Marker (contentTopMarkerName, newArea.top), undoManager); - setMarker (false, Marker (contentBottomMarkerName, newArea.bottom), undoManager); + currentState->fillType = fillType; } -ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const +void LowLevelGraphicsSoftwareRenderer::setOpacity (float newOpacity) { - return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY); + currentState->fillType.setOpacity (newOpacity); } -ValueTree DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::setInterpolationQuality (Graphics::ResamplingQuality quality) { - return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); + currentState->interpolationQuality = quality; } -int DrawableComposite::ValueTreeWrapper::getNumMarkers (bool xAxis) const +void LowLevelGraphicsSoftwareRenderer::fillRect (const Rectangle& r, const bool replaceExistingContents) { - return getMarkerList (xAxis).getNumChildren(); + currentState->fillRect (image, r, replaceExistingContents); } -const ValueTree DrawableComposite::ValueTreeWrapper::getMarkerState (bool xAxis, int index) const +void LowLevelGraphicsSoftwareRenderer::fillPath (const Path& path, const AffineTransform& transform) { - return getMarkerList (xAxis).getChild (index); + currentState->fillPath (image, path, transform); } -const ValueTree DrawableComposite::ValueTreeWrapper::getMarkerState (bool xAxis, const String& name) const +void LowLevelGraphicsSoftwareRenderer::drawImage (const Image& sourceImage, const Rectangle& srcClip, + const AffineTransform& transform, const bool fillEntireClipAsTiles) { - return getMarkerList (xAxis).getChildWithProperty (nameProperty, name); -} + jassert (sourceImage.getBounds().contains (srcClip)); -bool DrawableComposite::ValueTreeWrapper::containsMarker (bool xAxis, const ValueTree& state) const -{ - return state.isAChildOf (getMarkerList (xAxis)); + currentState->renderImage (image, sourceImage, srcClip, transform, + fillEntireClipAsTiles ? currentState->clip : 0); } -const DrawableComposite::Marker DrawableComposite::ValueTreeWrapper::getMarker (bool xAxis, const ValueTree& state) const +void LowLevelGraphicsSoftwareRenderer::drawLine (const Line & line) { - jassert (containsMarker (xAxis, state)); - - return Marker (state [nameProperty], RelativeCoordinate (state [posProperty].toString(), xAxis)); + Path p; + p.addLineSegment (line, 1.0f); + fillPath (p, AffineTransform::identity); } -void DrawableComposite::ValueTreeWrapper::setMarker (bool xAxis, const Marker& m, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, float top, float bottom) { - ValueTree markerList (getMarkerListCreating (xAxis, undoManager)); - ValueTree marker (markerList.getChildWithProperty (nameProperty, m.name)); - - if (marker.isValid()) - { - marker.setProperty (posProperty, m.position.toString(), undoManager); - } - else - { - marker = ValueTree (markerTag); - marker.setProperty (nameProperty, m.name, 0); - marker.setProperty (posProperty, m.position.toString(), 0); - markerList.addChild (marker, -1, undoManager); - } + if (bottom > top) + currentState->fillRect (image, Rectangle ((float) x, top, 1.0f, bottom - top)); } -void DrawableComposite::ValueTreeWrapper::removeMarker (bool xAxis, const ValueTree& state, UndoManager* undoManager) +void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, float left, float right) { - if (state [nameProperty].toString() != contentLeftMarkerName - && state [nameProperty].toString() != contentRightMarkerName - && state [nameProperty].toString() != contentTopMarkerName - && state [nameProperty].toString() != contentBottomMarkerName) - return getMarkerList (xAxis).removeChild (state, undoManager); + if (right > left) + currentState->fillRect (image, Rectangle (left, (float) y, right - left, 1.0f)); } -const Rectangle DrawableComposite::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) +class LowLevelGraphicsSoftwareRenderer::CachedGlyph { - const ValueTreeWrapper wrapper (tree); - setName (wrapper.getID()); - - Rectangle damage; - bool redrawAll = false; +public: + CachedGlyph() : glyph (0), lastAccessCount (0) {} + ~CachedGlyph() {} - const RelativeParallelogram newBounds (wrapper.getBoundingBox()); - if (bounds != newBounds) + void draw (SavedState& state, Image& image, const float x, const float y) const { - redrawAll = true; - damage = getBounds(); - bounds = newBounds; + if (edgeTable != 0) + state.fillEdgeTable (image, *edgeTable, x, roundToInt (y)); } - const int numMarkersX = wrapper.getNumMarkers (true); - const int numMarkersY = wrapper.getNumMarkers (false); - - // Remove deleted markers... - if (markersX.size() > numMarkersX || markersY.size() > numMarkersY) + void generate (const Font& newFont, const int glyphNumber) { - if (! redrawAll) - { - redrawAll = true; - damage = getBounds(); - } - - markersX.removeRange (jmax (2, numMarkersX), markersX.size()); - markersY.removeRange (jmax (2, numMarkersY), markersY.size()); - } + font = newFont; + glyph = glyphNumber; + edgeTable = 0; - // Update markers and add new ones.. - int i; - for (i = 0; i < numMarkersX; ++i) - { - const Marker newMarker (wrapper.getMarker (true, wrapper.getMarkerState (true, i))); - Marker* m = markersX[i]; + Path glyphPath; + font.getTypeface()->getOutlineForGlyph (glyphNumber, glyphPath); - if (m == 0 || newMarker != *m) + if (! glyphPath.isEmpty()) { - if (! redrawAll) - { - redrawAll = true; - damage = getBounds(); - } + const float fontHeight = font.getHeight(); + const AffineTransform transform (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) + .translated (0.0f, -0.5f)); - if (m == 0) - markersX.add (new Marker (newMarker)); - else - *m = newMarker; + edgeTable = new EdgeTable (glyphPath.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + glyphPath, transform); } } - for (i = 0; i < numMarkersY; ++i) - { - const Marker newMarker (wrapper.getMarker (false, wrapper.getMarkerState (false, i))); - Marker* m = markersY[i]; + int glyph, lastAccessCount; + Font font; - if (m == 0 || newMarker != *m) - { - if (! redrawAll) - { - redrawAll = true; - damage = getBounds(); - } + juce_UseDebuggingNewOperator - if (m == 0) - markersY.add (new Marker (newMarker)); - else - *m = newMarker; - } - } +private: + ScopedPointer edgeTable; - // Remove deleted drawables.. - for (i = drawables.size(); --i >= wrapper.getNumDrawables();) - { - Drawable* const d = drawables.getUnchecked(i); + CachedGlyph (const CachedGlyph&); + CachedGlyph& operator= (const CachedGlyph&); +}; - if (! redrawAll) - damage = damage.getUnion (d->getBounds()); +class LowLevelGraphicsSoftwareRenderer::GlyphCache : private DeletedAtShutdown +{ +public: + GlyphCache() + : accessCounter (0), hits (0), misses (0) + { + for (int i = 120; --i >= 0;) + glyphs.add (new CachedGlyph()); + } - d->parent = 0; - drawables.remove (i); + ~GlyphCache() + { + clearSingletonInstance(); } - // Update drawables and add new ones.. - for (i = 0; i < wrapper.getNumDrawables(); ++i) + juce_DeclareSingleton_SingleThreaded_Minimal (GlyphCache); + + void drawGlyph (SavedState& state, Image& image, const Font& font, const int glyphNumber, float x, float y) { - const ValueTree newDrawable (wrapper.getDrawableState (i)); - Drawable* d = drawables[i]; + ++accessCounter; + int oldestCounter = std::numeric_limits::max(); + CachedGlyph* oldest = 0; - if (d != 0) + for (int i = glyphs.size(); --i >= 0;) { - if (newDrawable.hasType (d->getValueTreeType())) - { - const Rectangle area (d->refreshFromValueTree (newDrawable, imageProvider)); + CachedGlyph* const glyph = glyphs.getUnchecked (i); - if (! redrawAll) - damage = damage.getUnion (area); - } - else + if (glyph->glyph == glyphNumber && glyph->font == font) { - if (! redrawAll) - damage = damage.getUnion (d->getBounds()); - - d = createChildFromValueTree (this, newDrawable, imageProvider); - drawables.set (i, d); + ++hits; + glyph->lastAccessCount = accessCounter; + glyph->draw (state, image, x, y); + return; + } - if (! redrawAll) - damage = damage.getUnion (d->getBounds()); + if (glyph->lastAccessCount <= oldestCounter) + { + oldestCounter = glyph->lastAccessCount; + oldest = glyph; } } - else + + if (hits + ++misses > (glyphs.size() << 4)) { - d = createChildFromValueTree (this, newDrawable, imageProvider); - drawables.set (i, d); + if (misses * 2 > hits) + { + for (int i = 32; --i >= 0;) + glyphs.add (new CachedGlyph()); + } - if (! redrawAll) - damage = damage.getUnion (d->getBounds()); + hits = misses = 0; + oldest = glyphs.getLast(); } - } - - if (redrawAll) - damage = damage.getUnion (getBounds()); - else if (! damage.isEmpty()) - damage = damage.transformed (calculateTransform()); - - return damage; -} - -const ValueTree DrawableComposite::createValueTree (ImageProvider* imageProvider) const -{ - ValueTree tree (valueTreeType); - ValueTreeWrapper v (tree); - v.setID (getName(), 0); - v.setBoundingBox (bounds, 0); - - int i; - for (i = 0; i < drawables.size(); ++i) - v.addDrawable (drawables.getUnchecked(i)->createValueTree (imageProvider), -1, 0); - - for (i = 0; i < markersX.size(); ++i) - v.setMarker (true, *markersX.getUnchecked(i), 0); - - for (i = 0; i < markersY.size(); ++i) - v.setMarker (false, *markersY.getUnchecked(i), 0); - - return tree; -} + jassert (oldest != 0); + oldest->lastAccessCount = accessCounter; + oldest->generate (font, glyphNumber); + oldest->draw (state, image, x, y); + } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_DrawableComposite.cpp ***/ + juce_UseDebuggingNewOperator +private: + friend class OwnedArray ; + OwnedArray glyphs; + int accessCounter, hits, misses; -/*** Start of inlined file: juce_DrawableImage.cpp ***/ -BEGIN_JUCE_NAMESPACE + GlyphCache (const GlyphCache&); + GlyphCache& operator= (const GlyphCache&); +}; -DrawableImage::DrawableImage() - : image (0), - opacity (1.0f), - overlayColour (0x00000000) -{ - bounds.topRight = RelativePoint (Point (1.0f, 0.0f)); - bounds.bottomLeft = RelativePoint (Point (0.0f, 1.0f)); -} +juce_ImplementSingleton_SingleThreaded (LowLevelGraphicsSoftwareRenderer::GlyphCache); -DrawableImage::DrawableImage (const DrawableImage& other) - : image (other.image), - opacity (other.opacity), - overlayColour (other.overlayColour), - bounds (other.bounds) +void LowLevelGraphicsSoftwareRenderer::setFont (const Font& newFont) { + currentState->font = newFont; } -DrawableImage::~DrawableImage() +const Font LowLevelGraphicsSoftwareRenderer::getFont() { + return currentState->font; } -void DrawableImage::setImage (const Image& imageToUse) +void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) { - image = imageToUse; + Font& f = currentState->font; - if (image.isValid()) + if (transform.isOnlyTranslation()) { - bounds.topLeft = RelativePoint (Point (0.0f, 0.0f)); - bounds.topRight = RelativePoint (Point ((float) image.getWidth(), 0.0f)); - bounds.bottomLeft = RelativePoint (Point (0.0f, (float) image.getHeight())); + GlyphCache::getInstance()->drawGlyph (*currentState, image, f, glyphNumber, + transform.getTranslationX(), + transform.getTranslationY()); + } + else + { + Path p; + f.getTypeface()->getOutlineForGlyph (glyphNumber, p); + fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()).followedBy (transform)); } } -void DrawableImage::setOpacity (const float newOpacity) -{ - opacity = newOpacity; -} +#if JUCE_MSVC + #pragma warning (pop) -void DrawableImage::setOverlayColour (const Colour& newOverlayColour) + #if JUCE_DEBUG + #pragma optimize ("", on) // resets optimisations to the project defaults + #endif +#endif + +END_JUCE_NAMESPACE +/*** End of inlined file: juce_LowLevelGraphicsSoftwareRenderer.cpp ***/ + + +/*** Start of inlined file: juce_RectanglePlacement.cpp ***/ +BEGIN_JUCE_NAMESPACE + +RectanglePlacement::RectanglePlacement (const RectanglePlacement& other) throw() + : flags (other.flags) { - overlayColour = newOverlayColour; } -void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds) +RectanglePlacement& RectanglePlacement::operator= (const RectanglePlacement& other) throw() { - bounds = newBounds; + flags = other.flags; + return *this; } -const AffineTransform DrawableImage::calculateTransform() const +void RectanglePlacement::applyTo (double& x, double& y, + double& w, double& h, + const double dx, const double dy, + const double dw, const double dh) const throw() { - if (image.isNull()) - return AffineTransform::identity; + if (w == 0 || h == 0) + return; - Point resolved[3]; - bounds.resolveThreePoints (resolved, parent); + if ((flags & stretchToFit) != 0) + { + x = dx; + y = dy; + w = dw; + h = dh; + } + else + { + double scale = (flags & fillDestination) != 0 ? jmax (dw / w, dh / h) + : jmin (dw / w, dh / h); - const Point tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth()); - const Point bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight()); + if ((flags & onlyReduceInSize) != 0) + scale = jmin (scale, 1.0); - return AffineTransform::fromTargetPoints (resolved[0].getX(), resolved[0].getY(), - tr.getX(), tr.getY(), - bl.getX(), bl.getY()); -} + if ((flags & onlyIncreaseInSize) != 0) + scale = jmax (scale, 1.0); -void DrawableImage::render (const Drawable::RenderingContext& context) const -{ - if (image.isValid()) - { - const AffineTransform t (calculateTransform().followedBy (context.transform)); + w *= scale; + h *= scale; - if (opacity > 0.0f && ! overlayColour.isOpaque()) - { - context.g.setOpacity (context.opacity * opacity); - context.g.drawImageTransformed (image, image.getBounds(), t, false); - } + if ((flags & xLeft) != 0) + x = dx; + else if ((flags & xRight) != 0) + x = dx + dw - w; + else + x = dx + (dw - w) * 0.5; - if (! overlayColour.isTransparent()) - { - context.g.setColour (overlayColour.withMultipliedAlpha (context.opacity)); - context.g.drawImageTransformed (image, image.getBounds(), t, true); - } + if ((flags & yTop) != 0) + y = dy; + else if ((flags & yBottom) != 0) + y = dy + dh - h; + else + y = dy + (dh - h) * 0.5; } } -const Rectangle DrawableImage::getBounds() const -{ - if (image.isNull()) - return Rectangle(); - - return bounds.getBounds (parent); -} - -bool DrawableImage::hitTest (float x, float y) const +const AffineTransform RectanglePlacement::getTransformToFit (float x, float y, + float w, float h, + const float dx, const float dy, + const float dw, const float dh) const throw() { - if (image.isNull()) - return false; - - calculateTransform().inverted().transformPoint (x, y); + if (w == 0 || h == 0) + return AffineTransform::identity; - const int ix = roundToInt (x); - const int iy = roundToInt (y); + const float scaleX = dw / w; + const float scaleY = dh / h; - return ix >= 0 - && iy >= 0 - && ix < image.getWidth() - && iy < image.getHeight() - && image.getPixelAt (ix, iy).getAlpha() >= 127; -} + if ((flags & stretchToFit) != 0) + return AffineTransform::translation (-x, -y) + .scaled (scaleX, scaleY) + .translated (dx, dy); -Drawable* DrawableImage::createCopy() const -{ - return new DrawableImage (*this); -} + float scale = (flags & fillDestination) != 0 ? jmax (scaleX, scaleY) + : jmin (scaleX, scaleY); -void DrawableImage::invalidatePoints() -{ -} + if ((flags & onlyReduceInSize) != 0) + scale = jmin (scale, 1.0f); -const Identifier DrawableImage::valueTreeType ("Image"); + if ((flags & onlyIncreaseInSize) != 0) + scale = jmax (scale, 1.0f); -const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity"); -const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay"); -const Identifier DrawableImage::ValueTreeWrapper::image ("image"); -const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft"); -const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight"); -const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft"); + w *= scale; + h *= scale; -DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) - : ValueTreeWrapperBase (state_) -{ - jassert (state.hasType (valueTreeType)); -} + float newX = dx; -const var DrawableImage::ValueTreeWrapper::getImageIdentifier() const -{ - return state [image]; -} + if ((flags & xRight) != 0) + newX += dw - w; // right + else if ((flags & xLeft) == 0) + newX += (dw - w) / 2.0f; // centre -Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager) -{ - return state.getPropertyAsValue (image, undoManager); -} + float newY = dy; -void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager) -{ - state.setProperty (image, newIdentifier, undoManager); -} + if ((flags & yBottom) != 0) + newY += dh - h; // bottom + else if ((flags & yTop) == 0) + newY += (dh - h) / 2.0f; // centre -float DrawableImage::ValueTreeWrapper::getOpacity() const -{ - return (float) state.getProperty (opacity, 1.0); + return AffineTransform::translation (-x, -y) + .scaled (scale, scale) + .translated (newX, newY); } -Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager) -{ - if (! state.hasProperty (opacity)) - state.setProperty (opacity, 1.0, undoManager); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_RectanglePlacement.cpp ***/ - return state.getPropertyAsValue (opacity, undoManager); -} -void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager) -{ - state.setProperty (opacity, newOpacity, undoManager); -} +/*** Start of inlined file: juce_Drawable.cpp ***/ +BEGIN_JUCE_NAMESPACE -const Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const +Drawable::RenderingContext::RenderingContext (Graphics& g_, + const AffineTransform& transform_, + const float opacity_) throw() + : g (g_), + transform (transform_), + opacity (opacity_) { - return Colour (state [overlay].toString().getHexValue32()); } -void DrawableImage::ValueTreeWrapper::setOverlayColour (const Colour& newColour, UndoManager* undoManager) +Drawable::Drawable() + : parent (0) { - if (newColour.isTransparent()) - state.removeProperty (overlay, undoManager); - else - state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager); } -Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager) +Drawable::~Drawable() { - return state.getPropertyAsValue (overlay, undoManager); } -const RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const +void Drawable::draw (Graphics& g, const float opacity, const AffineTransform& transform) const { - return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), - state.getProperty (topRight, "100, 0"), - state.getProperty (bottomLeft, "0, 100")); + render (RenderingContext (g, transform, opacity)); } -void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +void Drawable::drawAt (Graphics& g, const float x, const float y, const float opacity) const { - state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); - state.setProperty (topRight, newBounds.topRight.toString(), undoManager); - state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); + draw (g, opacity, AffineTransform::translation (x, y)); } -const Rectangle DrawableImage::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) +void Drawable::drawWithin (Graphics& g, + const int destX, + const int destY, + const int destW, + const int destH, + const RectanglePlacement& placement, + const float opacity) const { - const ValueTreeWrapper controller (tree); - setName (controller.getID()); - - const float newOpacity = controller.getOpacity(); - const Colour newOverlayColour (controller.getOverlayColour()); - - Image newImage; - const var imageIdentifier (controller.getImageIdentifier()); - - jassert (imageProvider != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them! - - if (imageProvider != 0) - newImage = imageProvider->getImageForIdentifier (imageIdentifier); - - const RelativeParallelogram newBounds (controller.getBoundingBox()); - - if (newOpacity != opacity || overlayColour != newOverlayColour || image != newImage || bounds != newBounds) + if (destW > 0 && destH > 0) { - const Rectangle damage (getBounds()); - - opacity = newOpacity; - overlayColour = newOverlayColour; - bounds = newBounds; - image = newImage; + Rectangle bounds (getBounds()); - return damage.getUnion (getBounds()); + draw (g, opacity, + placement.getTransformToFit (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), + (float) destX, (float) destY, + (float) destW, (float) destH)); } - - return Rectangle(); } -const ValueTree DrawableImage::createValueTree (ImageProvider* imageProvider) const +Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes) { - ValueTree tree (valueTreeType); - ValueTreeWrapper v (tree); + Drawable* result = 0; - v.setID (getName(), 0); - v.setOpacity (opacity, 0); - v.setOverlayColour (overlayColour, 0); - v.setBoundingBox (bounds, 0); + Image image (ImageFileFormat::loadFrom (data, (int) numBytes)); if (image.isValid()) { - jassert (imageProvider != 0); // if you're using images, you need to provide something that can load and save them! - - if (imageProvider != 0) - v.setImageIdentifier (imageProvider->getIdentifierForImage (image), 0); + DrawableImage* const di = new DrawableImage(); + di->setImage (image); + result = di; } + else + { + const String asString (String::createStringFromData (data, (int) numBytes)); - return tree; -} - -END_JUCE_NAMESPACE -/*** End of inlined file: juce_DrawableImage.cpp ***/ + XmlDocument doc (asString); + ScopedPointer outer (doc.getDocumentElement (true)); + if (outer != 0 && outer->hasTagName ("svg")) + { + ScopedPointer svg (doc.getDocumentElement()); -/*** Start of inlined file: juce_DrawablePath.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (svg != 0) + result = Drawable::createFromSVG (*svg); + } + } -DrawablePath::DrawablePath() - : mainFill (Colours::black), - strokeFill (Colours::black), - strokeType (0.0f), - pathNeedsUpdating (true), - strokeNeedsUpdating (true) -{ + return result; } -DrawablePath::DrawablePath (const DrawablePath& other) - : mainFill (other.mainFill), - strokeFill (other.strokeFill), - strokeType (other.strokeType), - pathNeedsUpdating (true), - strokeNeedsUpdating (true) +Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) { - if (other.relativePath != 0) - relativePath = new RelativePointPath (*other.relativePath); - else - path = other.path; -} + MemoryBlock mb; + dataSource.readIntoMemoryBlock (mb); -DrawablePath::~DrawablePath() -{ + return createFromImageData (mb.getData(), mb.getSize()); } -void DrawablePath::setPath (const Path& newPath) +Drawable* Drawable::createFromImageFile (const File& file) { - path = newPath; - strokeNeedsUpdating = true; -} + const ScopedPointer fin (file.createInputStream()); -void DrawablePath::setFill (const FillType& newFill) -{ - mainFill = newFill; + return fin != 0 ? createFromImageDataStream (*fin) : 0; } -void DrawablePath::setStrokeFill (const FillType& newFill) +Drawable* Drawable::createFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) { - strokeFill = newFill; + return createChildFromValueTree (0, tree, imageProvider); } -void DrawablePath::setStrokeType (const PathStrokeType& newStrokeType) +Drawable* Drawable::createChildFromValueTree (DrawableComposite* parent, const ValueTree& tree, ImageProvider* imageProvider) { - strokeType = newStrokeType; - strokeNeedsUpdating = true; -} + const Identifier type (tree.getType()); -void DrawablePath::setStrokeThickness (const float newThickness) -{ - setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle())); -} + Drawable* d = 0; + if (type == DrawablePath::valueTreeType) + d = new DrawablePath(); + else if (type == DrawableComposite::valueTreeType) + d = new DrawableComposite(); + else if (type == DrawableImage::valueTreeType) + d = new DrawableImage(); + else if (type == DrawableText::valueTreeType) + d = new DrawableText(); -void DrawablePath::updatePath() const -{ - if (pathNeedsUpdating) + if (d != 0) { - pathNeedsUpdating = false; - - if (relativePath != 0) - { - path.clear(); - relativePath->createPath (path, parent); - strokeNeedsUpdating = true; - } + d->parent = parent; + d->refreshFromValueTree (tree, imageProvider); } -} -void DrawablePath::updateStroke() const -{ - if (strokeNeedsUpdating) - { - strokeNeedsUpdating = false; - updatePath(); - stroke.clear(); - strokeType.createStrokedPath (stroke, path, AffineTransform::identity, 4.0f); - } + return d; } -const Path& DrawablePath::getPath() const +const Identifier Drawable::ValueTreeWrapperBase::idProperty ("id"); +const Identifier Drawable::ValueTreeWrapperBase::type ("type"); +const Identifier Drawable::ValueTreeWrapperBase::gradientPoint1 ("point1"); +const Identifier Drawable::ValueTreeWrapperBase::gradientPoint2 ("point2"); +const Identifier Drawable::ValueTreeWrapperBase::gradientPoint3 ("point3"); +const Identifier Drawable::ValueTreeWrapperBase::colour ("colour"); +const Identifier Drawable::ValueTreeWrapperBase::radial ("radial"); +const Identifier Drawable::ValueTreeWrapperBase::colours ("colours"); +const Identifier Drawable::ValueTreeWrapperBase::imageId ("imageId"); +const Identifier Drawable::ValueTreeWrapperBase::imageOpacity ("imageOpacity"); + +Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& state_) + : state (state_) { - updatePath(); - return path; } -const Path& DrawablePath::getStrokePath() const +Drawable::ValueTreeWrapperBase::~ValueTreeWrapperBase() { - updateStroke(); - return stroke; } -bool DrawablePath::isStrokeVisible() const throw() +const String Drawable::ValueTreeWrapperBase::getID() const { - return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.isInvisible(); + return state [idProperty]; } -void DrawablePath::invalidatePoints() +void Drawable::ValueTreeWrapperBase::setID (const String& newID, UndoManager* const undoManager) { - pathNeedsUpdating = true; - strokeNeedsUpdating = true; + if (newID.isEmpty()) + state.removeProperty (idProperty, undoManager); + else + state.setProperty (idProperty, newID, undoManager); } -void DrawablePath::render (const Drawable::RenderingContext& context) const +const FillType Drawable::ValueTreeWrapperBase::readFillType (const ValueTree& v, RelativePoint* const gp1, RelativePoint* const gp2, RelativePoint* const gp3, + RelativeCoordinate::NamedCoordinateFinder* const nameFinder, ImageProvider* imageProvider) { - { - FillType f (mainFill); - if (f.isGradient()) - f.gradient->multiplyOpacity (context.opacity); + const String newType (v[type].toString()); - f.transform = f.transform.followedBy (context.transform); - context.g.setFillType (f); - context.g.fillPath (getPath(), context.transform); + if (newType == "solid") + { + const String colourString (v [colour].toString()); + return FillType (Colour (colourString.isEmpty() ? (uint32) 0xff000000 + : (uint32) colourString.getHexValue32())); } - - if (isStrokeVisible()) + else if (newType == "gradient") { - FillType f (strokeFill); - if (f.isGradient()) - f.gradient->multiplyOpacity (context.opacity); + RelativePoint p1 (v [gradientPoint1]), p2 (v [gradientPoint2]), p3 (v [gradientPoint3]); - f.transform = f.transform.followedBy (context.transform); - context.g.setFillType (f); - context.g.fillPath (getStrokePath(), context.transform); - } -} + ColourGradient g; -const Rectangle DrawablePath::getBounds() const -{ - if (isStrokeVisible()) - return getStrokePath().getBounds(); - else - return getPath().getBounds(); -} + if (gp1 != 0) *gp1 = p1; + if (gp2 != 0) *gp2 = p2; + if (gp3 != 0) *gp3 = p3; -bool DrawablePath::hitTest (float x, float y) const -{ - return getPath().contains (x, y) - || (isStrokeVisible() && getStrokePath().contains (x, y)); -} + g.point1 = p1.resolve (nameFinder); + g.point2 = p2.resolve (nameFinder); + g.isRadial = v[radial]; -Drawable* DrawablePath::createCopy() const -{ - return new DrawablePath (*this); -} + StringArray colourSteps; + colourSteps.addTokens (v[colours].toString(), false); -const Identifier DrawablePath::valueTreeType ("Path"); + for (int i = 0; i < colourSteps.size() / 2; ++i) + g.addColour (colourSteps[i * 2].getDoubleValue(), + Colour ((uint32) colourSteps[i * 2 + 1].getHexValue32())); -const Identifier DrawablePath::ValueTreeWrapper::fill ("Fill"); -const Identifier DrawablePath::ValueTreeWrapper::stroke ("Stroke"); -const Identifier DrawablePath::ValueTreeWrapper::path ("Path"); -const Identifier DrawablePath::ValueTreeWrapper::jointStyle ("jointStyle"); -const Identifier DrawablePath::ValueTreeWrapper::capStyle ("capStyle"); -const Identifier DrawablePath::ValueTreeWrapper::strokeWidth ("strokeWidth"); -const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); -const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); -const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); -const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); + FillType fillType (g); + + if (g.isRadial) + { + const Point point3 (p3.resolve (nameFinder)); + const Point point3Source (g.point1.getX() + g.point2.getY() - g.point1.getY(), + g.point1.getY() + g.point1.getX() - g.point2.getX()); + + fillType.transform = AffineTransform::fromTargetPoints (g.point1.getX(), g.point1.getY(), g.point1.getX(), g.point1.getY(), + g.point2.getX(), g.point2.getY(), g.point2.getX(), g.point2.getY(), + point3Source.getX(), point3Source.getY(), point3.getX(), point3.getY()); + } + + return fillType; + } + else if (newType == "image") + { + Image im; + if (imageProvider != 0) + im = imageProvider->getImageForIdentifier (v[imageId]); -DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) - : ValueTreeWrapperBase (state_) -{ - jassert (state.hasType (valueTreeType)); -} + FillType f (im, AffineTransform::identity); + f.setOpacity ((float) v.getProperty (imageOpacity, 1.0f)); + return f; + } -ValueTree DrawablePath::ValueTreeWrapper::getPathState() -{ - return state.getOrCreateChildWithName (path, 0); + jassertfalse; + return FillType(); } -ValueTree DrawablePath::ValueTreeWrapper::getMainFillState() +static const Point calcThirdGradientPoint (const FillType& fillType) { - ValueTree v (state.getChildWithName (fill)); - if (v.isValid()) - return v; + const ColourGradient& g = *fillType.gradient; + const Point point3Source (g.point1.getX() + g.point2.getY() - g.point1.getY(), + g.point1.getY() + g.point1.getX() - g.point2.getX()); - setMainFill (Colours::black, 0, 0, 0, 0, 0); - return getMainFillState(); + return point3Source.transformedBy (fillType.transform); } -ValueTree DrawablePath::ValueTreeWrapper::getStrokeFillState() +void Drawable::ValueTreeWrapperBase::writeFillType (ValueTree& v, const FillType& fillType, + const RelativePoint* const gp1, const RelativePoint* const gp2, const RelativePoint* gp3, + ImageProvider* imageProvider, UndoManager* const undoManager) { - ValueTree v (state.getChildWithName (stroke)); - if (v.isValid()) - return v; + if (fillType.isColour()) + { + v.setProperty (type, "solid", undoManager); + v.setProperty (colour, String::toHexString ((int) fillType.colour.getARGB()), undoManager); + } + else if (fillType.isGradient()) + { + v.setProperty (type, "gradient", undoManager); + v.setProperty (gradientPoint1, gp1 != 0 ? gp1->toString() : fillType.gradient->point1.toString(), undoManager); + v.setProperty (gradientPoint2, gp2 != 0 ? gp2->toString() : fillType.gradient->point2.toString(), undoManager); + v.setProperty (gradientPoint3, gp3 != 0 ? gp3->toString() : calcThirdGradientPoint (fillType).toString(), undoManager); - setStrokeFill (Colours::black, 0, 0, 0, 0, 0); - return getStrokeFillState(); -} + v.setProperty (radial, fillType.gradient->isRadial, undoManager); -const FillType DrawablePath::ValueTreeWrapper::getMainFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder, - ImageProvider* imageProvider) const -{ - return readFillType (state.getChildWithName (fill), 0, 0, 0, nameFinder, imageProvider); -} + String s; + for (int i = 0; i < fillType.gradient->getNumColours(); ++i) + s << ' ' << fillType.gradient->getColourPosition (i) + << ' ' << String::toHexString ((int) fillType.gradient->getColour(i).getARGB()); -void DrawablePath::ValueTreeWrapper::setMainFill (const FillType& newFill, const RelativePoint* gp1, - const RelativePoint* gp2, const RelativePoint* gp3, - ImageProvider* imageProvider, UndoManager* undoManager) -{ - ValueTree v (state.getOrCreateChildWithName (fill, undoManager)); - writeFillType (v, newFill, gp1, gp2, gp3, imageProvider, undoManager); -} + v.setProperty (colours, s.trimStart(), undoManager); + } + else if (fillType.isTiledImage()) + { + v.setProperty (type, "image", undoManager); -const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder, - ImageProvider* imageProvider) const -{ - return readFillType (state.getChildWithName (stroke), 0, 0, 0, nameFinder, imageProvider); -} + if (imageProvider != 0) + v.setProperty (imageId, imageProvider->getIdentifierForImage (fillType.image), undoManager); -void DrawablePath::ValueTreeWrapper::setStrokeFill (const FillType& newFill, const RelativePoint* gp1, - const RelativePoint* gp2, const RelativePoint* gp3, - ImageProvider* imageProvider, UndoManager* undoManager) -{ - ValueTree v (state.getOrCreateChildWithName (stroke, undoManager)); - writeFillType (v, newFill, gp1, gp2, gp3, imageProvider, undoManager); + if (fillType.getOpacity() < 1.0f) + v.setProperty (imageOpacity, fillType.getOpacity(), undoManager); + else + v.removeProperty (imageOpacity, undoManager); + } + else + { + jassertfalse; + } } -const PathStrokeType DrawablePath::ValueTreeWrapper::getStrokeType() const -{ - const String jointStyleString (state [jointStyle].toString()); - const String capStyleString (state [capStyle].toString()); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_Drawable.cpp ***/ - return PathStrokeType (state [strokeWidth], - jointStyleString == "curved" ? PathStrokeType::curved - : (jointStyleString == "bevel" ? PathStrokeType::beveled - : PathStrokeType::mitered), - capStyleString == "square" ? PathStrokeType::square - : (capStyleString == "round" ? PathStrokeType::rounded - : PathStrokeType::butt)); -} -void DrawablePath::ValueTreeWrapper::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager) -{ - state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager); - state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered - ? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager); - state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt - ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); -} +/*** Start of inlined file: juce_DrawableComposite.cpp ***/ +BEGIN_JUCE_NAMESPACE -bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const +DrawableComposite::DrawableComposite() + : bounds (Point(), Point (100.0f, 0.0f), Point (0.0f, 100.0f)) { - return state [nonZeroWinding]; + setContentArea (RelativeRectangle (RelativeCoordinate (0.0), + RelativeCoordinate (100.0), + RelativeCoordinate (0.0), + RelativeCoordinate (100.0))); } -void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) +DrawableComposite::DrawableComposite (const DrawableComposite& other) { - state.setProperty (nonZeroWinding, b, undoManager); -} + bounds = other.bounds; -const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode"); -const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); -const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); -const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); -const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); -const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); + for (int i = 0; i < other.drawables.size(); ++i) + drawables.add (other.drawables.getUnchecked(i)->createCopy()); -const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner"; -const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round"; -const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm"; + markersX.addCopiesOf (other.markersX); + markersY.addCopiesOf (other.markersY); +} -DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) - : state (state_) +DrawableComposite::~DrawableComposite() { } -DrawablePath::ValueTreeWrapper::Element::~Element() +void DrawableComposite::insertDrawable (Drawable* drawable, const int index) { + if (drawable != 0) + { + jassert (! drawables.contains (drawable)); // trying to add a drawable that's already in here! + jassert (drawable->parent == 0); // A drawable can only live inside one parent at a time! + drawables.insert (index, drawable); + drawable->parent = this; + } } -DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const +void DrawableComposite::insertDrawable (const Drawable& drawable, const int index) { - return ValueTreeWrapper (state.getParent().getParent()); + insertDrawable (drawable.createCopy(), index); } -DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const +void DrawableComposite::removeDrawable (const int index, const bool deleteDrawable) { - return Element (state.getSibling (-1)); + drawables.remove (index, deleteDrawable); } -int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const throw() +Drawable* DrawableComposite::getDrawableWithName (const String& name) const throw() { - const Identifier i (state.getType()); - if (i == startSubPathElement || i == lineToElement) return 1; - if (i == quadraticToElement) return 2; - if (i == cubicToElement) return 3; + for (int i = drawables.size(); --i >= 0;) + if (drawables.getUnchecked(i)->getName() == name) + return drawables.getUnchecked(i); + return 0; } -const RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const +void DrawableComposite::bringToFront (const int index) { - jassert (index >= 0 && index < getNumControlPoints()); - return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); + if (index >= 0 && index < drawables.size() - 1) + drawables.move (index, -1); } -Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager) const +void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBoundingBox) { - jassert (index >= 0 && index < getNumControlPoints()); - return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager); + bounds = newBoundingBox; } -void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) +DrawableComposite::Marker::Marker (const DrawableComposite::Marker& other) + : name (other.name), position (other.position) { - jassert (index >= 0 && index < getNumControlPoints()); - return state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); } -const RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const +DrawableComposite::Marker::Marker (const String& name_, const RelativeCoordinate& position_) + : name (name_), position (position_) { - const Identifier i (state.getType()); - - if (i == startSubPathElement) - return getControlPoint (0); - - jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement); - - return getPreviousElement().getEndPoint(); } -const RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const +bool DrawableComposite::Marker::operator!= (const DrawableComposite::Marker& other) const throw() { - const Identifier i (state.getType()); - if (i == startSubPathElement || i == lineToElement) return getControlPoint (0); - if (i == quadraticToElement) return getControlPoint (1); - if (i == cubicToElement) return getControlPoint (2); - - jassert (i == closeSubPathElement); - return RelativePoint(); + return name != other.name || position != other.position; } -float DrawablePath::ValueTreeWrapper::Element::getLength (RelativeCoordinate::NamedCoordinateFinder* nameFinder) const -{ - const Identifier i (state.getType()); - - if (i == lineToElement || i == closeSubPathElement) - return getEndPoint().resolve (nameFinder).getDistanceFrom (getStartPoint().resolve (nameFinder)); - - if (i == cubicToElement) - { - Path p; - p.startNewSubPath (getStartPoint().resolve (nameFinder)); - p.cubicTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder), getControlPoint (2).resolve (nameFinder)); - return p.getLength(); - } - - if (i == quadraticToElement) - { - Path p; - p.startNewSubPath (getStartPoint().resolve (nameFinder)); - p.quadraticTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder)); - return p.getLength(); - } - - jassert (i == startSubPathElement); - return 0; -} +const char* const DrawableComposite::contentLeftMarkerName ("left"); +const char* const DrawableComposite::contentRightMarkerName ("right"); +const char* const DrawableComposite::contentTopMarkerName ("top"); +const char* const DrawableComposite::contentBottomMarkerName ("bottom"); -const String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const +const RelativeRectangle DrawableComposite::getContentArea() const { - return state [mode].toString(); -} + jassert (markersX.size() >= 2 && getMarker (true, 0)->name == contentLeftMarkerName && getMarker (true, 1)->name == contentRightMarkerName); + jassert (markersY.size() >= 2 && getMarker (false, 0)->name == contentTopMarkerName && getMarker (false, 1)->name == contentBottomMarkerName); -void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager) -{ - if (state.hasType (cubicToElement)) - state.setProperty (mode, newMode, undoManager); + return RelativeRectangle (markersX.getUnchecked(0)->position, markersX.getUnchecked(1)->position, + markersY.getUnchecked(0)->position, markersY.getUnchecked(1)->position); } -void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager) +void DrawableComposite::setContentArea (const RelativeRectangle& newArea) { - const Identifier i (state.getType()); - - if (i == quadraticToElement || i == cubicToElement) - { - ValueTree newState (lineToElement); - Element e (newState); - e.setControlPoint (0, getEndPoint(), undoManager); - state = newState; - } + setMarker (contentLeftMarkerName, true, newArea.left); + setMarker (contentRightMarkerName, true, newArea.right); + setMarker (contentTopMarkerName, false, newArea.top); + setMarker (contentBottomMarkerName, false, newArea.bottom); } -void DrawablePath::ValueTreeWrapper::Element::convertToCubic (RelativeCoordinate::NamedCoordinateFinder* nameFinder, UndoManager* undoManager) +void DrawableComposite::resetBoundingBoxToContentArea() { - const Identifier i (state.getType()); - - if (i == lineToElement || i == quadraticToElement) - { - ValueTree newState (cubicToElement); - Element e (newState); - - const RelativePoint start (getStartPoint()); - const RelativePoint end (getEndPoint()); - const Point startResolved (start.resolve (nameFinder)); - const Point endResolved (end.resolve (nameFinder)); - e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager); - e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager); - e.setControlPoint (2, end, undoManager); + const RelativeRectangle content (getContentArea()); - state = newState; - } + setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), + RelativePoint (content.right, content.top), + RelativePoint (content.left, content.bottom))); } -void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager) +void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() { - const Identifier i (state.getType()); + const Rectangle bounds (getUntransformedBounds (false)); - if (i != startSubPathElement) - { - ValueTree newState (startSubPathElement); - Element e (newState); - e.setControlPoint (0, getEndPoint(), undoManager); - state = newState; - } + setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX()), + RelativeCoordinate (bounds.getRight()), + RelativeCoordinate (bounds.getY()), + RelativeCoordinate (bounds.getBottom()))); + resetBoundingBoxToContentArea(); } -static const Point findCubicSubdivisionPoint (float proportion, const Point points[4]) +int DrawableComposite::getNumMarkers (const bool xAxis) const throw() { - const Point mid1 (points[0] + (points[1] - points[0]) * proportion), - mid2 (points[1] + (points[2] - points[1]) * proportion), - mid3 (points[2] + (points[3] - points[2]) * proportion); - - const Point newCp1 (mid1 + (mid2 - mid1) * proportion), - newCp2 (mid2 + (mid3 - mid2) * proportion); - - return newCp1 + (newCp2 - newCp1) * proportion; + return (xAxis ? markersX : markersY).size(); } -static const Point findQuadraticSubdivisionPoint (float proportion, const Point points[3]) +const DrawableComposite::Marker* DrawableComposite::getMarker (const bool xAxis, const int index) const throw() { - const Point mid1 (points[0] + (points[1] - points[0]) * proportion), - mid2 (points[1] + (points[2] - points[1]) * proportion); - - return mid1 + (mid2 - mid1) * proportion; + return (xAxis ? markersX : markersY) [index]; } -float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (const Point& targetPoint, RelativeCoordinate::NamedCoordinateFinder* nameFinder) const +void DrawableComposite::setMarker (const String& name, const bool xAxis, const RelativeCoordinate& position) { - const Identifier i (state.getType()); - float bestProp = 0; + OwnedArray & markers = (xAxis ? markersX : markersY); - if (i == cubicToElement) + for (int i = 0; i < markers.size(); ++i) { - RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); - - const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) }; - - float bestDistance = std::numeric_limits::max(); - - for (int i = 110; --i >= 0;) + Marker* const m = markers.getUnchecked(i); + if (m->name == name) { - float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); - const Point centre (findCubicSubdivisionPoint (prop, points)); - const float distance = centre.getDistanceFrom (targetPoint); - - if (distance < bestDistance) + if (m->position != position) { - bestProp = prop; - bestDistance = distance; + m->position = position; + invalidatePoints(); } - } - } - else if (i == quadraticToElement) - { - RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); - const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) }; - - float bestDistance = std::numeric_limits::max(); - - for (int i = 110; --i >= 0;) - { - float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); - const Point centre (findQuadraticSubdivisionPoint ((float) prop, points)); - const float distance = centre.getDistanceFrom (targetPoint); - if (distance < bestDistance) - { - bestProp = prop; - bestDistance = distance; - } + return; } } - else if (i == lineToElement) - { - RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); - const Line line (rp1.resolve (nameFinder), rp2.resolve (nameFinder)); - bestProp = line.findNearestProportionalPositionTo (targetPoint); - } - return bestProp; + (xAxis ? markersX : markersY).add (new Marker (name, position)); + invalidatePoints(); } -ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (const Point& targetPoint, RelativeCoordinate::NamedCoordinateFinder* nameFinder, UndoManager* undoManager) +void DrawableComposite::removeMarker (const bool xAxis, const int index) { - ValueTree newTree; - const Identifier i (state.getType()); - - if (i == cubicToElement) - { - float bestProp = findProportionAlongLine (targetPoint, nameFinder); - - RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); - const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) }; - - const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), - mid2 (points[1] + (points[2] - points[1]) * bestProp), - mid3 (points[2] + (points[3] - points[2]) * bestProp); - - const Point newCp1 (mid1 + (mid2 - mid1) * bestProp), - newCp2 (mid2 + (mid3 - mid2) * bestProp); - - const Point newCentre (newCp1 + (newCp2 - newCp1) * bestProp); - - setControlPoint (0, mid1, undoManager); - setControlPoint (1, newCp1, undoManager); - setControlPoint (2, newCentre, undoManager); - setModeOfEndPoint (roundedMode, undoManager); - - Element newElement (newTree = ValueTree (cubicToElement)); - newElement.setControlPoint (0, newCp2, 0); - newElement.setControlPoint (1, mid3, 0); - newElement.setControlPoint (2, rp4, 0); - - state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); - } - else if (i == quadraticToElement) - { - float bestProp = findProportionAlongLine (targetPoint, nameFinder); - - RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); - const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) }; + jassert (index >= 2); - const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), - mid2 (points[1] + (points[2] - points[1]) * bestProp); + if (index >= 2) + (xAxis ? markersX : markersY).remove (index); +} - const Point newCentre (mid1 + (mid2 - mid1) * bestProp); +const AffineTransform DrawableComposite::calculateTransform() const +{ + Point resolved[3]; + bounds.resolveThreePoints (resolved, parent); - setControlPoint (0, mid1, undoManager); - setControlPoint (1, newCentre, undoManager); - setModeOfEndPoint (roundedMode, undoManager); + const Rectangle content (getContentArea().resolve (parent)); - Element newElement (newTree = ValueTree (quadraticToElement)); - newElement.setControlPoint (0, mid2, 0); - newElement.setControlPoint (1, rp3, 0); + return AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].getX(), resolved[0].getY(), + content.getRight(), content.getY(), resolved[1].getX(), resolved[1].getY(), + content.getX(), content.getBottom(), resolved[2].getX(), resolved[2].getY()); +} - state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); - } - else if (i == lineToElement) +void DrawableComposite::render (const Drawable::RenderingContext& context) const +{ + if (drawables.size() > 0 && context.opacity > 0) { - RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); - const Line line (rp1.resolve (nameFinder), rp2.resolve (nameFinder)); - const Point newPoint (line.findNearestPointTo (targetPoint)); + if (context.opacity >= 1.0f || drawables.size() == 1) + { + Drawable::RenderingContext contextCopy (context); + contextCopy.transform = calculateTransform().followedBy (context.transform); - setControlPoint (0, newPoint, undoManager); + for (int i = 0; i < drawables.size(); ++i) + drawables.getUnchecked(i)->render (contextCopy); + } + else + { + // To correctly render a whole composite layer with an overall transparency, + // we need to render everything opaquely into a temp buffer, then blend that + // with the target opacity... + const Rectangle clipBounds (context.g.getClipBounds()); + Image tempImage (Image::ARGB, clipBounds.getWidth(), clipBounds.getHeight(), true); - Element newElement (newTree = ValueTree (lineToElement)); - newElement.setControlPoint (0, rp2, 0); + { + Graphics tempG (tempImage); + tempG.setOrigin (-clipBounds.getX(), -clipBounds.getY()); + Drawable::RenderingContext tempContext (tempG, context.transform, 1.0f); + render (tempContext); + } - state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); - } - else if (i == closeSubPathElement) - { + context.g.setOpacity (context.opacity); + context.g.drawImageAt (tempImage, clipBounds.getX(), clipBounds.getY()); + } } - - return newTree; -} - -void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager) -{ - state.getParent().removeChild (state, undoManager); } -const Rectangle DrawablePath::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) +const RelativeCoordinate DrawableComposite::findNamedCoordinate (const String& objectName, const String& edge) const { - Rectangle damageRect; - ValueTreeWrapper v (tree); - setName (v.getID()); - - bool needsRedraw = false; - const FillType newFill (v.getMainFill (parent, imageProvider)); + if (objectName == RelativeCoordinate::Strings::parent) + { + if (edge == RelativeCoordinate::Strings::right || edge == RelativeCoordinate::Strings::bottom) + { + jassertfalse; // a Drawable doesn't have a fixed right-hand or bottom edge - use a marker instead if you need a point of reference. + return RelativeCoordinate (100.0); + } + } - if (mainFill != newFill) + int i; + for (i = 0; i < markersX.size(); ++i) { - needsRedraw = true; - mainFill = newFill; + Marker* const m = markersX.getUnchecked(i); + if (m->name == objectName) + return m->position; } - const FillType newStrokeFill (v.getStrokeFill (parent, imageProvider)); - - if (strokeFill != newStrokeFill) + for (i = 0; i < markersY.size(); ++i) { - needsRedraw = true; - strokeFill = newStrokeFill; + Marker* const m = markersY.getUnchecked(i); + if (m->name == objectName) + return m->position; } - const PathStrokeType newStroke (v.getStrokeType()); - - ScopedPointer newRelativePath (new RelativePointPath (tree)); + return RelativeCoordinate(); +} - Path newPath; - newRelativePath->createPath (newPath, parent); +const Rectangle DrawableComposite::getUntransformedBounds (const bool includeMarkers) const +{ + Rectangle bounds; - if (! newRelativePath->containsAnyDynamicPoints()) - newRelativePath = 0; + int i; + for (i = 0; i < drawables.size(); ++i) + bounds = bounds.getUnion (drawables.getUnchecked(i)->getBounds()); - if (strokeType != newStroke || path != newPath) + if (includeMarkers) { - damageRect = getBounds(); - path.swapWithPath (newPath); - strokeNeedsUpdating = true; - strokeType = newStroke; - needsRedraw = true; - } + if (markersX.size() > 0) + { + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::min(); - relativePath = newRelativePath; + for (i = markersX.size(); --i >= 0;) + { + const Marker* m = markersX.getUnchecked(i); + const float pos = (float) m->position.resolve (this); + minX = jmin (minX, pos); + maxX = jmax (maxX, pos); + } - if (needsRedraw) - damageRect = damageRect.getUnion (getBounds()); + if (minX <= maxX) + { + if (bounds.getHeight() > 0) + { + minX = jmin (minX, bounds.getX()); + maxX = jmax (maxX, bounds.getRight()); + } - return damageRect; -} + bounds.setLeft (minX); + bounds.setWidth (maxX - minX); + } + } -const ValueTree DrawablePath::createValueTree (ImageProvider* imageProvider) const -{ - ValueTree tree (valueTreeType); - ValueTreeWrapper v (tree); + if (markersY.size() > 0) + { + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::min(); - v.setID (getName(), 0); - v.setMainFill (mainFill, 0, 0, 0, imageProvider, 0); - v.setStrokeFill (strokeFill, 0, 0, 0, imageProvider, 0); - v.setStrokeType (strokeType, 0); + for (i = markersY.size(); --i >= 0;) + { + const Marker* m = markersY.getUnchecked(i); + const float pos = (float) m->position.resolve (this); + minY = jmin (minY, pos); + maxY = jmax (maxY, pos); + } - if (relativePath != 0) - { - relativePath->writeTo (tree, 0); - } - else - { - RelativePointPath rp (path); - rp.writeTo (tree, 0); + if (minY <= maxY) + { + if (bounds.getHeight() > 0) + { + minY = jmin (minY, bounds.getY()); + maxY = jmax (maxY, bounds.getBottom()); + } + + bounds.setTop (minY); + bounds.setHeight (maxY - minY); + } + } } - return tree; + return bounds; } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_DrawablePath.cpp ***/ - - -/*** Start of inlined file: juce_DrawableText.cpp ***/ -BEGIN_JUCE_NAMESPACE - -DrawableText::DrawableText() - : colour (Colours::black), - justification (Justification::centredLeft) +const Rectangle DrawableComposite::getBounds() const { - setFont (Font (15.0f), true); + return getUntransformedBounds (true).transformed (calculateTransform()); } -DrawableText::DrawableText (const DrawableText& other) - : text (other.text), - font (other.font), - colour (other.colour), - justification (other.justification), - bounds (other.bounds), - fontSizeControlPoint (other.fontSizeControlPoint) +bool DrawableComposite::hitTest (float x, float y) const { + calculateTransform().inverted().transformPoint (x, y); + + for (int i = 0; i < drawables.size(); ++i) + if (drawables.getUnchecked(i)->hitTest (x, y)) + return true; + + return false; } -DrawableText::~DrawableText() +Drawable* DrawableComposite::createCopy() const { + return new DrawableComposite (*this); } -void DrawableText::setText (const String& newText) +void DrawableComposite::invalidatePoints() { - text = newText; + for (int i = 0; i < drawables.size(); ++i) + drawables.getUnchecked(i)->invalidatePoints(); } -void DrawableText::setColour (const Colour& newColour) +const Identifier DrawableComposite::valueTreeType ("Group"); + +const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft"); +const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables"); +const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX"); +const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY"); +const Identifier DrawableComposite::ValueTreeWrapper::markerTag ("Marker"); +const Identifier DrawableComposite::ValueTreeWrapper::nameProperty ("name"); +const Identifier DrawableComposite::ValueTreeWrapper::posProperty ("position"); + +DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) { - colour = newColour; + jassert (state.hasType (valueTreeType)); } -void DrawableText::setFont (const Font& newFont, bool applySizeAndScale) +ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const { - font = newFont; - - if (applySizeAndScale) - { - Point corners[3]; - bounds.resolveThreePoints (corners, parent); - - setFontSizeControlPoint (RelativePoint (RelativeParallelogram::getPointForInternalCoord (corners, - Point (font.getHorizontalScale() * font.getHeight(), font.getHeight())))); - } + return state.getChildWithName (childGroupTag); } -void DrawableText::setJustification (const Justification& newJustification) +ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) { - justification = newJustification; + return state.getOrCreateChildWithName (childGroupTag, undoManager); } -void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) +int DrawableComposite::ValueTreeWrapper::getNumDrawables() const { - bounds = newBounds; + return getChildList().getNumChildren(); } -void DrawableText::setFontSizeControlPoint (const RelativePoint& newPoint) +ValueTree DrawableComposite::ValueTreeWrapper::getDrawableState (int index) const { - fontSizeControlPoint = newPoint; + return getChildList().getChild (index); } -void DrawableText::render (const Drawable::RenderingContext& context) const +ValueTree DrawableComposite::ValueTreeWrapper::getDrawableWithId (const String& objectId, bool recursive) const { - Point points[3]; - bounds.resolveThreePoints (points, parent); + if (getID() == objectId) + return state; - const float w = Line (points[0], points[1]).getLength(); - const float h = Line (points[0], points[2]).getLength(); + if (! recursive) + { + return getChildList().getChildWithProperty (idProperty, objectId); + } + else + { + const ValueTree childList (getChildList()); - const Point fontCoords (bounds.getInternalCoordForPoint (points, fontSizeControlPoint.resolve (parent))); - const float fontHeight = jlimit (1.0f, h, fontCoords.getY()); - const float fontWidth = jlimit (0.01f, w, fontCoords.getX()); + for (int i = getNumDrawables(); --i >= 0;) + { + const ValueTree& child = childList.getChild (i); - Font f (font); - f.setHeight (fontHeight); - f.setHorizontalScale (fontWidth / fontHeight); + if (child [Drawable::ValueTreeWrapperBase::idProperty] == objectId) + return child; - context.g.setColour (colour.withMultipliedAlpha (context.opacity)); + if (child.hasType (DrawableComposite::valueTreeType)) + { + ValueTree v (DrawableComposite::ValueTreeWrapper (child).getDrawableWithId (objectId, true)); - GlyphArrangement ga; - ga.addFittedText (f, text, 0, 0, w, h, justification, 0x100000); - ga.draw (context.g, - AffineTransform::fromTargetPoints (0, 0, points[0].getX(), points[0].getY(), - w, 0, points[1].getX(), points[1].getY(), - 0, h, points[2].getX(), points[2].getY()) - .followedBy (context.transform)); -} + if (v.isValid()) + return v; + } + } -const Rectangle DrawableText::getBounds() const -{ - return bounds.getBounds (parent); + return ValueTree::invalid; + } } -bool DrawableText::hitTest (float x, float y) const +int DrawableComposite::ValueTreeWrapper::indexOfDrawable (const ValueTree& item) const { - Path p; - bounds.getPath (p, parent); - return p.contains (x, y); + return getChildList().indexOf (item); } -Drawable* DrawableText::createCopy() const +void DrawableComposite::ValueTreeWrapper::addDrawable (const ValueTree& newDrawableState, int index, UndoManager* undoManager) { - return new DrawableText (*this); + getChildListCreating (undoManager).addChild (newDrawableState, index, undoManager); } -void DrawableText::invalidatePoints() +void DrawableComposite::ValueTreeWrapper::moveDrawableOrder (int currentIndex, int newIndex, UndoManager* undoManager) { + getChildListCreating (undoManager).moveChild (currentIndex, newIndex, undoManager); } -const Identifier DrawableText::valueTreeType ("Text"); - -const Identifier DrawableText::ValueTreeWrapper::text ("text"); -const Identifier DrawableText::ValueTreeWrapper::colour ("colour"); -const Identifier DrawableText::ValueTreeWrapper::font ("font"); -const Identifier DrawableText::ValueTreeWrapper::justification ("justification"); -const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft"); -const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight"); -const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft"); -const Identifier DrawableText::ValueTreeWrapper::fontSizeAnchor ("fontSizeAnchor"); - -DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) - : ValueTreeWrapperBase (state_) +void DrawableComposite::ValueTreeWrapper::removeDrawable (const ValueTree& child, UndoManager* undoManager) { - jassert (state.hasType (valueTreeType)); + getChildList().removeChild (child, undoManager); } -const String DrawableText::ValueTreeWrapper::getText() const +const RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const { - return state [text].toString(); + return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), + state.getProperty (topRight, "100, 0"), + state.getProperty (bottomLeft, "0, 100")); } -void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) +void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) { - state.setProperty (text, newText, undoManager); + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); } -Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager) +void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager) { - return state.getPropertyAsValue (text, undoManager); -} + const RelativeRectangle content (getContentArea()); -const Colour DrawableText::ValueTreeWrapper::getColour() const -{ - return Colour::fromString (state [colour].toString()); + setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), + RelativePoint (content.right, content.top), + RelativePoint (content.left, content.bottom)), undoManager); } -void DrawableText::ValueTreeWrapper::setColour (const Colour& newColour, UndoManager* undoManager) +const RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const { - state.setProperty (colour, newColour.toString(), undoManager); + return RelativeRectangle (getMarker (true, getMarkerState (true, 0)).position, + getMarker (true, getMarkerState (true, 1)).position, + getMarker (false, getMarkerState (false, 0)).position, + getMarker (false, getMarkerState (false, 1)).position); } -const Justification DrawableText::ValueTreeWrapper::getJustification() const +void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager) { - return Justification ((int) state [justification]); + setMarker (true, Marker (contentLeftMarkerName, newArea.left), undoManager); + setMarker (true, Marker (contentRightMarkerName, newArea.right), undoManager); + setMarker (false, Marker (contentTopMarkerName, newArea.top), undoManager); + setMarker (false, Marker (contentBottomMarkerName, newArea.bottom), undoManager); } -void DrawableText::ValueTreeWrapper::setJustification (const Justification& newJustification, UndoManager* undoManager) +ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const { - state.setProperty (justification, newJustification.getFlags(), undoManager); + return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY); } -const Font DrawableText::ValueTreeWrapper::getFont() const +ValueTree DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) { - return Font::fromString (state [font]); + return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); } -void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager) +int DrawableComposite::ValueTreeWrapper::getNumMarkers (bool xAxis) const { - state.setProperty (font, newFont.toString(), undoManager); + return getMarkerList (xAxis).getNumChildren(); } -Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager) +const ValueTree DrawableComposite::ValueTreeWrapper::getMarkerState (bool xAxis, int index) const { - return state.getPropertyAsValue (font, undoManager); + return getMarkerList (xAxis).getChild (index); } -const RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const +const ValueTree DrawableComposite::ValueTreeWrapper::getMarkerState (bool xAxis, const String& name) const { - return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString()); + return getMarkerList (xAxis).getChildWithProperty (nameProperty, name); } -void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +bool DrawableComposite::ValueTreeWrapper::containsMarker (bool xAxis, const ValueTree& state) const { - state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); - state.setProperty (topRight, newBounds.topRight.toString(), undoManager); - state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); + return state.isAChildOf (getMarkerList (xAxis)); } -const RelativePoint DrawableText::ValueTreeWrapper::getFontSizeControlPoint() const +const DrawableComposite::Marker DrawableComposite::ValueTreeWrapper::getMarker (bool xAxis, const ValueTree& state) const { - return state [fontSizeAnchor].toString(); -} + jassert (containsMarker (xAxis, state)); -void DrawableText::ValueTreeWrapper::setFontSizeControlPoint (const RelativePoint& p, UndoManager* undoManager) -{ - state.setProperty (fontSizeAnchor, p.toString(), undoManager); + return Marker (state [nameProperty], RelativeCoordinate (state [posProperty].toString(), xAxis)); } -const Rectangle DrawableText::refreshFromValueTree (const ValueTree& tree, ImageProvider*) +void DrawableComposite::ValueTreeWrapper::setMarker (bool xAxis, const Marker& m, UndoManager* undoManager) { - ValueTreeWrapper v (tree); - setName (v.getID()); - - const RelativeParallelogram newBounds (v.getBoundingBox()); - const RelativePoint newFontPoint (v.getFontSizeControlPoint()); - const Colour newColour (v.getColour()); - const Justification newJustification (v.getJustification()); - const String newText (v.getText()); - const Font newFont (v.getFont()); + ValueTree markerList (getMarkerListCreating (xAxis, undoManager)); + ValueTree marker (markerList.getChildWithProperty (nameProperty, m.name)); - if (text != newText || font != newFont || justification != newJustification - || colour != newColour || bounds != newBounds || newFontPoint != fontSizeControlPoint) + if (marker.isValid()) { - const Rectangle damage (getBounds()); - - setBoundingBox (newBounds); - setFontSizeControlPoint (newFontPoint); - setColour (newColour); - setFont (newFont, false); - setJustification (newJustification); - setText (newText); - - return damage.getUnion (getBounds()); - + marker.setProperty (posProperty, m.position.toString(), undoManager); + } + else + { + marker = ValueTree (markerTag); + marker.setProperty (nameProperty, m.name, 0); + marker.setProperty (posProperty, m.position.toString(), 0); + markerList.addChild (marker, -1, undoManager); } - - return Rectangle(); } -const ValueTree DrawableText::createValueTree (ImageProvider*) const +void DrawableComposite::ValueTreeWrapper::removeMarker (bool xAxis, const ValueTree& state, UndoManager* undoManager) { - ValueTree tree (valueTreeType); - ValueTreeWrapper v (tree); - - v.setID (getName(), 0); - v.setText (text, 0); - v.setFont (font, 0); - v.setJustification (justification, 0); - v.setColour (colour, 0); - v.setBoundingBox (bounds, 0); - v.setFontSizeControlPoint (fontSizeControlPoint, 0); - - return tree; + if (state [nameProperty].toString() != contentLeftMarkerName + && state [nameProperty].toString() != contentRightMarkerName + && state [nameProperty].toString() != contentTopMarkerName + && state [nameProperty].toString() != contentBottomMarkerName) + return getMarkerList (xAxis).removeChild (state, undoManager); } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_DrawableText.cpp ***/ - - -/*** Start of inlined file: juce_SVGParser.cpp ***/ -BEGIN_JUCE_NAMESPACE - -class SVGState +const Rectangle DrawableComposite::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) { -public: + const ValueTreeWrapper wrapper (tree); + setName (wrapper.getID()); - SVGState (const XmlElement* const topLevel) - : topLevelXml (topLevel), - elementX (0), elementY (0), - width (512), height (512), - viewBoxW (0), viewBoxH (0) - { - } + Rectangle damage; + bool redrawAll = false; - ~SVGState() + const RelativeParallelogram newBounds (wrapper.getBoundingBox()); + if (bounds != newBounds) { + redrawAll = true; + damage = getBounds(); + bounds = newBounds; } - Drawable* parseSVGElement (const XmlElement& xml) - { - if (! xml.hasTagName ("svg")) - return 0; - - DrawableComposite* const drawable = new DrawableComposite(); - - drawable->setName (xml.getStringAttribute ("id")); + const int numMarkersX = wrapper.getNumMarkers (true); + const int numMarkersY = wrapper.getNumMarkers (false); - SVGState newState (*this); + // Remove deleted markers... + if (markersX.size() > numMarkersX || markersY.size() > numMarkersY) + { + if (! redrawAll) + { + redrawAll = true; + damage = getBounds(); + } - if (xml.hasAttribute ("transform")) - newState.addTransform (xml); + markersX.removeRange (jmax (2, numMarkersX), markersX.size()); + markersY.removeRange (jmax (2, numMarkersY), markersY.size()); + } - newState.elementX = getCoordLength (xml.getStringAttribute ("x", String (newState.elementX)), viewBoxW); - newState.elementY = getCoordLength (xml.getStringAttribute ("y", String (newState.elementY)), viewBoxH); - newState.width = getCoordLength (xml.getStringAttribute ("width", String (newState.width)), viewBoxW); - newState.height = getCoordLength (xml.getStringAttribute ("height", String (newState.height)), viewBoxH); + // Update markers and add new ones.. + int i; + for (i = 0; i < numMarkersX; ++i) + { + const Marker newMarker (wrapper.getMarker (true, wrapper.getMarkerState (true, i))); + Marker* m = markersX[i]; - if (xml.hasAttribute ("viewBox")) + if (m == 0 || newMarker != *m) { - const String viewParams (xml.getStringAttribute ("viewBox")); - int i = 0; - float vx, vy, vw, vh; - - if (parseCoords (viewParams, vx, vy, i, true) - && parseCoords (viewParams, vw, vh, i, true) - && vw > 0 - && vh > 0) + if (! redrawAll) { - newState.viewBoxW = vw; - newState.viewBoxH = vh; - - int placementFlags = 0; - - const String aspect (xml.getStringAttribute ("preserveAspectRatio")); - - if (aspect.containsIgnoreCase ("none")) - { - placementFlags = RectanglePlacement::stretchToFit; - } - else - { - if (aspect.containsIgnoreCase ("slice")) - placementFlags |= RectanglePlacement::fillDestination; - - if (aspect.containsIgnoreCase ("xMin")) - placementFlags |= RectanglePlacement::xLeft; - else if (aspect.containsIgnoreCase ("xMax")) - placementFlags |= RectanglePlacement::xRight; - else - placementFlags |= RectanglePlacement::xMid; - - if (aspect.containsIgnoreCase ("yMin")) - placementFlags |= RectanglePlacement::yTop; - else if (aspect.containsIgnoreCase ("yMax")) - placementFlags |= RectanglePlacement::yBottom; - else - placementFlags |= RectanglePlacement::yMid; - } - - const RectanglePlacement placement (placementFlags); - - newState.transform - = placement.getTransformToFit (vx, vy, vw, vh, - 0.0f, 0.0f, newState.width, newState.height) - .followedBy (newState.transform); + redrawAll = true; + damage = getBounds(); } - } - else - { - if (viewBoxW == 0) - newState.viewBoxW = newState.width; - - if (viewBoxH == 0) - newState.viewBoxH = newState.height; - } - newState.parseSubElements (xml, drawable); - - drawable->resetContentAreaAndBoundingBoxToFitChildren(); - return drawable; + if (m == 0) + markersX.add (new Marker (newMarker)); + else + *m = newMarker; + } } -private: - - const XmlElement* const topLevelXml; - float elementX, elementY, width, height, viewBoxW, viewBoxH; - AffineTransform transform; - String cssStyleText; - - void parseSubElements (const XmlElement& xml, DrawableComposite* const parentDrawable) + for (i = 0; i < numMarkersY; ++i) { - forEachXmlChildElement (xml, e) - { - Drawable* d = 0; + const Marker newMarker (wrapper.getMarker (false, wrapper.getMarkerState (false, i))); + Marker* m = markersY[i]; - if (e->hasTagName ("g")) d = parseGroupElement (*e); - else if (e->hasTagName ("svg")) d = parseSVGElement (*e); - else if (e->hasTagName ("path")) d = parsePath (*e); - else if (e->hasTagName ("rect")) d = parseRect (*e); - else if (e->hasTagName ("circle")) d = parseCircle (*e); - else if (e->hasTagName ("ellipse")) d = parseEllipse (*e); - else if (e->hasTagName ("line")) d = parseLine (*e); - else if (e->hasTagName ("polyline")) d = parsePolygon (*e, true); - else if (e->hasTagName ("polygon")) d = parsePolygon (*e, false); - else if (e->hasTagName ("text")) d = parseText (*e); - else if (e->hasTagName ("switch")) d = parseSwitch (*e); - else if (e->hasTagName ("style")) parseCSSStyle (*e); + if (m == 0 || newMarker != *m) + { + if (! redrawAll) + { + redrawAll = true; + damage = getBounds(); + } - parentDrawable->insertDrawable (d); + if (m == 0) + markersY.add (new Marker (newMarker)); + else + *m = newMarker; } } - DrawableComposite* parseSwitch (const XmlElement& xml) + // Remove deleted drawables.. + for (i = drawables.size(); --i >= wrapper.getNumDrawables();) { - const XmlElement* const group = xml.getChildByName ("g"); + Drawable* const d = drawables.getUnchecked(i); - if (group != 0) - return parseGroupElement (*group); + if (! redrawAll) + damage = damage.getUnion (d->getBounds()); - return 0; + d->parent = 0; + drawables.remove (i); } - DrawableComposite* parseGroupElement (const XmlElement& xml) + // Update drawables and add new ones.. + for (i = 0; i < wrapper.getNumDrawables(); ++i) { - DrawableComposite* const drawable = new DrawableComposite(); - - drawable->setName (xml.getStringAttribute ("id")); + const ValueTree newDrawable (wrapper.getDrawableState (i)); + Drawable* d = drawables[i]; - if (xml.hasAttribute ("transform")) + if (d != 0) { - SVGState newState (*this); - newState.addTransform (xml); + if (newDrawable.hasType (d->getValueTreeType())) + { + const Rectangle area (d->refreshFromValueTree (newDrawable, imageProvider)); - newState.parseSubElements (xml, drawable); + if (! redrawAll) + damage = damage.getUnion (area); + } + else + { + if (! redrawAll) + damage = damage.getUnion (d->getBounds()); + + d = createChildFromValueTree (this, newDrawable, imageProvider); + drawables.set (i, d); + + if (! redrawAll) + damage = damage.getUnion (d->getBounds()); + } } else { - parseSubElements (xml, drawable); - } + d = createChildFromValueTree (this, newDrawable, imageProvider); + drawables.set (i, d); - drawable->resetContentAreaAndBoundingBoxToFitChildren(); - return drawable; + if (! redrawAll) + damage = damage.getUnion (d->getBounds()); + } } - Drawable* parsePath (const XmlElement& xml) const - { - const String d (xml.getStringAttribute ("d").trimStart()); - Path path; + if (redrawAll) + damage = damage.getUnion (getBounds()); + else if (! damage.isEmpty()) + damage = damage.transformed (calculateTransform()); - if (getStyleAttribute (&xml, "fill-rule").trim().equalsIgnoreCase ("evenodd")) - path.setUsingNonZeroWinding (false); + return damage; +} - int index = 0; - float lastX = 0, lastY = 0; - float lastX2 = 0, lastY2 = 0; - juce_wchar lastCommandChar = 0; - bool isRelative = true; - bool carryOn = true; +const ValueTree DrawableComposite::createValueTree (ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); - const String validCommandChars ("MmLlHhVvCcSsQqTtAaZz"); + v.setID (getName(), 0); + v.setBoundingBox (bounds, 0); - while (d[index] != 0) - { - float x, y, x2, y2, x3, y3; + int i; + for (i = 0; i < drawables.size(); ++i) + v.addDrawable (drawables.getUnchecked(i)->createValueTree (imageProvider), -1, 0); - if (validCommandChars.containsChar (d[index])) - { - lastCommandChar = d [index++]; - isRelative = (lastCommandChar >= 'a' && lastCommandChar <= 'z'); - } + for (i = 0; i < markersX.size(); ++i) + v.setMarker (true, *markersX.getUnchecked(i), 0); - switch (lastCommandChar) - { - case 'M': - case 'm': - case 'L': - case 'l': - if (parseCoords (d, x, y, index, false)) - { - if (isRelative) - { - x += lastX; - y += lastY; - } + for (i = 0; i < markersY.size(); ++i) + v.setMarker (false, *markersY.getUnchecked(i), 0); - if (lastCommandChar == 'M' || lastCommandChar == 'm') - { - path.startNewSubPath (x, y); - lastCommandChar = 'l'; - } - else - path.lineTo (x, y); + return tree; +} - lastX2 = lastX; - lastY2 = lastY; - lastX = x; - lastY = y; - } - else - { - ++index; - } +END_JUCE_NAMESPACE +/*** End of inlined file: juce_DrawableComposite.cpp ***/ - break; - case 'H': - case 'h': - if (parseCoord (d, x, index, false, true)) - { - if (isRelative) - x += lastX; +/*** Start of inlined file: juce_DrawableImage.cpp ***/ +BEGIN_JUCE_NAMESPACE - path.lineTo (x, lastY); +DrawableImage::DrawableImage() + : image (0), + opacity (1.0f), + overlayColour (0x00000000) +{ + bounds.topRight = RelativePoint (Point (1.0f, 0.0f)); + bounds.bottomLeft = RelativePoint (Point (0.0f, 1.0f)); +} - lastX2 = lastX; - lastX = x; - } - else - { - ++index; - } - break; +DrawableImage::DrawableImage (const DrawableImage& other) + : image (other.image), + opacity (other.opacity), + overlayColour (other.overlayColour), + bounds (other.bounds) +{ +} - case 'V': - case 'v': - if (parseCoord (d, y, index, false, false)) - { - if (isRelative) - y += lastY; +DrawableImage::~DrawableImage() +{ +} - path.lineTo (lastX, y); +void DrawableImage::setImage (const Image& imageToUse) +{ + image = imageToUse; - lastY2 = lastY; - lastY = y; - } - else - { - ++index; - } - break; + if (image.isValid()) + { + bounds.topLeft = RelativePoint (Point (0.0f, 0.0f)); + bounds.topRight = RelativePoint (Point ((float) image.getWidth(), 0.0f)); + bounds.bottomLeft = RelativePoint (Point (0.0f, (float) image.getHeight())); + } +} - case 'C': - case 'c': - if (parseCoords (d, x, y, index, false) - && parseCoords (d, x2, y2, index, false) - && parseCoords (d, x3, y3, index, false)) - { - if (isRelative) - { - x += lastX; - y += lastY; - x2 += lastX; - y2 += lastY; - x3 += lastX; - y3 += lastY; - } +void DrawableImage::setOpacity (const float newOpacity) +{ + opacity = newOpacity; +} - path.cubicTo (x, y, x2, y2, x3, y3); +void DrawableImage::setOverlayColour (const Colour& newOverlayColour) +{ + overlayColour = newOverlayColour; +} - lastX2 = x2; - lastY2 = y2; - lastX = x3; - lastY = y3; - } - else - { - ++index; - } - break; +void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds) +{ + bounds = newBounds; +} - case 'S': - case 's': - if (parseCoords (d, x, y, index, false) - && parseCoords (d, x3, y3, index, false)) - { - if (isRelative) - { - x += lastX; - y += lastY; - x3 += lastX; - y3 += lastY; - } +const AffineTransform DrawableImage::calculateTransform() const +{ + if (image.isNull()) + return AffineTransform::identity; - x2 = lastX + (lastX - lastX2); - y2 = lastY + (lastY - lastY2); - path.cubicTo (x2, y2, x, y, x3, y3); + Point resolved[3]; + bounds.resolveThreePoints (resolved, parent); - lastX2 = x; - lastY2 = y; - lastX = x3; - lastY = y3; - } - else - { - ++index; - } - break; + const Point tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth()); + const Point bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight()); - case 'Q': - case 'q': - if (parseCoords (d, x, y, index, false) - && parseCoords (d, x2, y2, index, false)) - { - if (isRelative) - { - x += lastX; - y += lastY; - x2 += lastX; - y2 += lastY; - } + return AffineTransform::fromTargetPoints (resolved[0].getX(), resolved[0].getY(), + tr.getX(), tr.getY(), + bl.getX(), bl.getY()); +} - path.quadraticTo (x, y, x2, y2); +void DrawableImage::render (const Drawable::RenderingContext& context) const +{ + if (image.isValid()) + { + const AffineTransform t (calculateTransform().followedBy (context.transform)); - lastX2 = x; - lastY2 = y; - lastX = x2; - lastY = y2; - } - else - { - ++index; - } - break; + if (opacity > 0.0f && ! overlayColour.isOpaque()) + { + context.g.setOpacity (context.opacity * opacity); + context.g.drawImageTransformed (image, image.getBounds(), t, false); + } - case 'T': - case 't': - if (parseCoords (d, x, y, index, false)) - { - if (isRelative) - { - x += lastX; - y += lastY; - } + if (! overlayColour.isTransparent()) + { + context.g.setColour (overlayColour.withMultipliedAlpha (context.opacity)); + context.g.drawImageTransformed (image, image.getBounds(), t, true); + } + } +} - x2 = lastX + (lastX - lastX2); - y2 = lastY + (lastY - lastY2); - path.quadraticTo (x2, y2, x, y); +const Rectangle DrawableImage::getBounds() const +{ + if (image.isNull()) + return Rectangle(); - lastX2 = x2; - lastY2 = y2; - lastX = x; - lastY = y; - } - else - { - ++index; - } - break; + return bounds.getBounds (parent); +} - case 'A': - case 'a': - if (parseCoords (d, x, y, index, false)) - { - String num; +bool DrawableImage::hitTest (float x, float y) const +{ + if (image.isNull()) + return false; - if (parseNextNumber (d, num, index, false)) - { - const float angle = num.getFloatValue() * (180.0f / float_Pi); + calculateTransform().inverted().transformPoint (x, y); - if (parseNextNumber (d, num, index, false)) - { - const bool largeArc = num.getIntValue() != 0; + const int ix = roundToInt (x); + const int iy = roundToInt (y); - if (parseNextNumber (d, num, index, false)) - { - const bool sweep = num.getIntValue() != 0; + return ix >= 0 + && iy >= 0 + && ix < image.getWidth() + && iy < image.getHeight() + && image.getPixelAt (ix, iy).getAlpha() >= 127; +} - if (parseCoords (d, x2, y2, index, false)) - { - if (isRelative) - { - x2 += lastX; - y2 += lastY; - } +Drawable* DrawableImage::createCopy() const +{ + return new DrawableImage (*this); +} - if (lastX != x2 || lastY != y2) - { - double centreX, centreY, startAngle, deltaAngle; - double rx = x, ry = y; +void DrawableImage::invalidatePoints() +{ +} - endpointToCentreParameters (lastX, lastY, x2, y2, - angle, largeArc, sweep, - rx, ry, centreX, centreY, - startAngle, deltaAngle); +const Identifier DrawableImage::valueTreeType ("Image"); - path.addCentredArc ((float) centreX, (float) centreY, - (float) rx, (float) ry, - angle, (float) startAngle, (float) (startAngle + deltaAngle), - false); +const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity"); +const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay"); +const Identifier DrawableImage::ValueTreeWrapper::image ("image"); +const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft"); - path.lineTo (x2, y2); - } +DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} - lastX2 = lastX; - lastY2 = lastY; - lastX = x2; - lastY = y2; - } - } - } - } - } - else - { - ++index; - } +const var DrawableImage::ValueTreeWrapper::getImageIdentifier() const +{ + return state [image]; +} - break; +Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (image, undoManager); +} + +void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager) +{ + state.setProperty (image, newIdentifier, undoManager); +} + +float DrawableImage::ValueTreeWrapper::getOpacity() const +{ + return (float) state.getProperty (opacity, 1.0); +} + +Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager) +{ + if (! state.hasProperty (opacity)) + state.setProperty (opacity, 1.0, undoManager); + + return state.getPropertyAsValue (opacity, undoManager); +} + +void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager) +{ + state.setProperty (opacity, newOpacity, undoManager); +} + +const Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const +{ + return Colour (state [overlay].toString().getHexValue32()); +} + +void DrawableImage::ValueTreeWrapper::setOverlayColour (const Colour& newColour, UndoManager* undoManager) +{ + if (newColour.isTransparent()) + state.removeProperty (overlay, undoManager); + else + state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager); +} + +Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (overlay, undoManager); +} + +const RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const +{ + return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), + state.getProperty (topRight, "100, 0"), + state.getProperty (bottomLeft, "0, 100")); +} + +void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +{ + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); +} + +const Rectangle DrawableImage::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) +{ + const ValueTreeWrapper controller (tree); + setName (controller.getID()); - case 'Z': - case 'z': - path.closeSubPath(); - while (CharacterFunctions::isWhitespace (d [index])) - ++index; + const float newOpacity = controller.getOpacity(); + const Colour newOverlayColour (controller.getOverlayColour()); - break; + Image newImage; + const var imageIdentifier (controller.getImageIdentifier()); - default: - carryOn = false; - break; - } + jassert (imageProvider != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them! - if (! carryOn) - break; - } + if (imageProvider != 0) + newImage = imageProvider->getImageForIdentifier (imageIdentifier); - return parseShape (xml, path); - } + const RelativeParallelogram newBounds (controller.getBoundingBox()); - Drawable* parseRect (const XmlElement& xml) const + if (newOpacity != opacity || overlayColour != newOverlayColour || image != newImage || bounds != newBounds) { - Path rect; + const Rectangle damage (getBounds()); - const bool hasRX = xml.hasAttribute ("rx"); - const bool hasRY = xml.hasAttribute ("ry"); + opacity = newOpacity; + overlayColour = newOverlayColour; + bounds = newBounds; + image = newImage; - if (hasRX || hasRY) - { - float rx = getCoordLength (xml.getStringAttribute ("rx"), viewBoxW); - float ry = getCoordLength (xml.getStringAttribute ("ry"), viewBoxH); + return damage.getUnion (getBounds()); + } - if (! hasRX) - rx = ry; - else if (! hasRY) - ry = rx; + return Rectangle(); +} - rect.addRoundedRectangle (getCoordLength (xml.getStringAttribute ("x"), viewBoxW), - getCoordLength (xml.getStringAttribute ("y"), viewBoxH), - getCoordLength (xml.getStringAttribute ("width"), viewBoxW), - getCoordLength (xml.getStringAttribute ("height"), viewBoxH), - rx, ry); - } - else - { - rect.addRectangle (getCoordLength (xml.getStringAttribute ("x"), viewBoxW), - getCoordLength (xml.getStringAttribute ("y"), viewBoxH), - getCoordLength (xml.getStringAttribute ("width"), viewBoxW), - getCoordLength (xml.getStringAttribute ("height"), viewBoxH)); - } +const ValueTree DrawableImage::createValueTree (ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); - return parseShape (xml, rect); - } + v.setID (getName(), 0); + v.setOpacity (opacity, 0); + v.setOverlayColour (overlayColour, 0); + v.setBoundingBox (bounds, 0); - Drawable* parseCircle (const XmlElement& xml) const + if (image.isValid()) { - Path circle; - - const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); - const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); - const float radius = getCoordLength (xml.getStringAttribute ("r"), viewBoxW); - - circle.addEllipse (cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); + jassert (imageProvider != 0); // if you're using images, you need to provide something that can load and save them! - return parseShape (xml, circle); + if (imageProvider != 0) + v.setImageIdentifier (imageProvider->getIdentifierForImage (image), 0); } - Drawable* parseEllipse (const XmlElement& xml) const - { - Path ellipse; + return tree; +} - const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); - const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); - const float radiusX = getCoordLength (xml.getStringAttribute ("rx"), viewBoxW); - const float radiusY = getCoordLength (xml.getStringAttribute ("ry"), viewBoxH); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_DrawableImage.cpp ***/ - ellipse.addEllipse (cx - radiusX, cy - radiusY, radiusX * 2.0f, radiusY * 2.0f); - return parseShape (xml, ellipse); - } +/*** Start of inlined file: juce_DrawablePath.cpp ***/ +BEGIN_JUCE_NAMESPACE - Drawable* parseLine (const XmlElement& xml) const - { - Path line; +DrawablePath::DrawablePath() + : mainFill (Colours::black), + strokeFill (Colours::black), + strokeType (0.0f), + pathNeedsUpdating (true), + strokeNeedsUpdating (true) +{ +} - const float x1 = getCoordLength (xml.getStringAttribute ("x1"), viewBoxW); - const float y1 = getCoordLength (xml.getStringAttribute ("y1"), viewBoxH); - const float x2 = getCoordLength (xml.getStringAttribute ("x2"), viewBoxW); - const float y2 = getCoordLength (xml.getStringAttribute ("y2"), viewBoxH); +DrawablePath::DrawablePath (const DrawablePath& other) + : mainFill (other.mainFill), + strokeFill (other.strokeFill), + strokeType (other.strokeType), + pathNeedsUpdating (true), + strokeNeedsUpdating (true) +{ + if (other.relativePath != 0) + relativePath = new RelativePointPath (*other.relativePath); + else + path = other.path; +} - line.startNewSubPath (x1, y1); - line.lineTo (x2, y2); +DrawablePath::~DrawablePath() +{ +} - return parseShape (xml, line); - } +void DrawablePath::setPath (const Path& newPath) +{ + path = newPath; + strokeNeedsUpdating = true; +} - Drawable* parsePolygon (const XmlElement& xml, const bool isPolyline) const - { - const String points (xml.getStringAttribute ("points")); - Path path; +void DrawablePath::setFill (const FillType& newFill) +{ + mainFill = newFill; +} - int index = 0; - float x, y; +void DrawablePath::setStrokeFill (const FillType& newFill) +{ + strokeFill = newFill; +} - if (parseCoords (points, x, y, index, true)) - { - float firstX = x; - float firstY = y; - float lastX = 0, lastY = 0; +void DrawablePath::setStrokeType (const PathStrokeType& newStrokeType) +{ + strokeType = newStrokeType; + strokeNeedsUpdating = true; +} - path.startNewSubPath (x, y); +void DrawablePath::setStrokeThickness (const float newThickness) +{ + setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle())); +} - while (parseCoords (points, x, y, index, true)) - { - lastX = x; - lastY = y; - path.lineTo (x, y); - } +void DrawablePath::updatePath() const +{ + if (pathNeedsUpdating) + { + pathNeedsUpdating = false; - if ((! isPolyline) || (firstX == lastX && firstY == lastY)) - path.closeSubPath(); + if (relativePath != 0) + { + path.clear(); + relativePath->createPath (path, parent); + strokeNeedsUpdating = true; } - - return parseShape (xml, path); } +} - Drawable* parseShape (const XmlElement& xml, Path& path, - const bool shouldParseTransform = true) const +void DrawablePath::updateStroke() const +{ + if (strokeNeedsUpdating) { - if (shouldParseTransform && xml.hasAttribute ("transform")) - { - SVGState newState (*this); - newState.addTransform (xml); - - return newState.parseShape (xml, path, false); - } - - DrawablePath* dp = new DrawablePath(); - dp->setName (xml.getStringAttribute ("id")); - dp->setFill (Colours::transparentBlack); + strokeNeedsUpdating = false; + updatePath(); + stroke.clear(); + strokeType.createStrokedPath (stroke, path, AffineTransform::identity, 4.0f); + } +} - path.applyTransform (transform); - dp->setPath (path); +const Path& DrawablePath::getPath() const +{ + updatePath(); + return path; +} - Path::Iterator iter (path); +const Path& DrawablePath::getStrokePath() const +{ + updateStroke(); + return stroke; +} - bool containsClosedSubPath = false; - while (iter.next()) - { - if (iter.elementType == Path::Iterator::closePath) - { - containsClosedSubPath = true; - break; - } - } +bool DrawablePath::isStrokeVisible() const throw() +{ + return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.isInvisible(); +} - dp->setFill (getPathFillType (path, - getStyleAttribute (&xml, "fill"), - getStyleAttribute (&xml, "fill-opacity"), - getStyleAttribute (&xml, "opacity"), - containsClosedSubPath ? Colours::black - : Colours::transparentBlack)); +void DrawablePath::invalidatePoints() +{ + pathNeedsUpdating = true; + strokeNeedsUpdating = true; +} - const String strokeType (getStyleAttribute (&xml, "stroke")); +void DrawablePath::render (const Drawable::RenderingContext& context) const +{ + { + FillType f (mainFill); + if (f.isGradient()) + f.gradient->multiplyOpacity (context.opacity); - if (strokeType.isNotEmpty() && ! strokeType.equalsIgnoreCase ("none")) - { - dp->setStrokeFill (getPathFillType (path, strokeType, - getStyleAttribute (&xml, "stroke-opacity"), - getStyleAttribute (&xml, "opacity"), - Colours::transparentBlack)); + f.transform = f.transform.followedBy (context.transform); + context.g.setFillType (f); + context.g.fillPath (getPath(), context.transform); + } - dp->setStrokeType (getStrokeFor (&xml)); - } + if (isStrokeVisible()) + { + FillType f (strokeFill); + if (f.isGradient()) + f.gradient->multiplyOpacity (context.opacity); - return dp; + f.transform = f.transform.followedBy (context.transform); + context.g.setFillType (f); + context.g.fillPath (getStrokePath(), context.transform); } +} - const XmlElement* findLinkedElement (const XmlElement* e) const - { - const String id (e->getStringAttribute ("xlink:href")); +const Rectangle DrawablePath::getBounds() const +{ + if (isStrokeVisible()) + return getStrokePath().getBounds(); + else + return getPath().getBounds(); +} - if (! id.startsWithChar ('#')) - return 0; +bool DrawablePath::hitTest (float x, float y) const +{ + return getPath().contains (x, y) + || (isStrokeVisible() && getStrokePath().contains (x, y)); +} - return findElementForId (topLevelXml, id.substring (1)); - } +Drawable* DrawablePath::createCopy() const +{ + return new DrawablePath (*this); +} - void addGradientStopsIn (ColourGradient& cg, const XmlElement* const fillXml) const - { - if (fillXml == 0) - return; +const Identifier DrawablePath::valueTreeType ("Path"); - forEachXmlChildElementWithTagName (*fillXml, e, "stop") - { - int index = 0; - Colour col (parseColour (getStyleAttribute (e, "stop-color"), index, Colours::black)); +const Identifier DrawablePath::ValueTreeWrapper::fill ("Fill"); +const Identifier DrawablePath::ValueTreeWrapper::stroke ("Stroke"); +const Identifier DrawablePath::ValueTreeWrapper::path ("Path"); +const Identifier DrawablePath::ValueTreeWrapper::jointStyle ("jointStyle"); +const Identifier DrawablePath::ValueTreeWrapper::capStyle ("capStyle"); +const Identifier DrawablePath::ValueTreeWrapper::strokeWidth ("strokeWidth"); +const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); +const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); +const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); +const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); - const String opacity (getStyleAttribute (e, "stop-opacity", "1")); - col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue())); +DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} - double offset = e->getDoubleAttribute ("offset"); +ValueTree DrawablePath::ValueTreeWrapper::getPathState() +{ + return state.getOrCreateChildWithName (path, 0); +} - if (e->getStringAttribute ("offset").containsChar ('%')) - offset *= 0.01; +ValueTree DrawablePath::ValueTreeWrapper::getMainFillState() +{ + ValueTree v (state.getChildWithName (fill)); + if (v.isValid()) + return v; - cg.addColour (jlimit (0.0, 1.0, offset), col); - } - } + setMainFill (Colours::black, 0, 0, 0, 0, 0); + return getMainFillState(); +} - const FillType getPathFillType (const Path& path, - const String& fill, - const String& fillOpacity, - const String& overallOpacity, - const Colour& defaultColour) const - { - float opacity = 1.0f; +ValueTree DrawablePath::ValueTreeWrapper::getStrokeFillState() +{ + ValueTree v (state.getChildWithName (stroke)); + if (v.isValid()) + return v; - if (overallOpacity.isNotEmpty()) - opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue()); + setStrokeFill (Colours::black, 0, 0, 0, 0, 0); + return getStrokeFillState(); +} - if (fillOpacity.isNotEmpty()) - opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); +const FillType DrawablePath::ValueTreeWrapper::getMainFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder, + ImageProvider* imageProvider) const +{ + return readFillType (state.getChildWithName (fill), 0, 0, 0, nameFinder, imageProvider); +} - if (fill.startsWithIgnoreCase ("url")) - { - const String id (fill.fromFirstOccurrenceOf ("#", false, false) - .upToLastOccurrenceOf (")", false, false).trim()); +void DrawablePath::ValueTreeWrapper::setMainFill (const FillType& newFill, const RelativePoint* gp1, + const RelativePoint* gp2, const RelativePoint* gp3, + ImageProvider* imageProvider, UndoManager* undoManager) +{ + ValueTree v (state.getOrCreateChildWithName (fill, undoManager)); + writeFillType (v, newFill, gp1, gp2, gp3, imageProvider, undoManager); +} - const XmlElement* const fillXml = findElementForId (topLevelXml, id); +const FillType DrawablePath::ValueTreeWrapper::getStrokeFill (RelativeCoordinate::NamedCoordinateFinder* nameFinder, + ImageProvider* imageProvider) const +{ + return readFillType (state.getChildWithName (stroke), 0, 0, 0, nameFinder, imageProvider); +} - if (fillXml != 0 - && (fillXml->hasTagName ("linearGradient") - || fillXml->hasTagName ("radialGradient"))) - { - const XmlElement* inheritedFrom = findLinkedElement (fillXml); +void DrawablePath::ValueTreeWrapper::setStrokeFill (const FillType& newFill, const RelativePoint* gp1, + const RelativePoint* gp2, const RelativePoint* gp3, + ImageProvider* imageProvider, UndoManager* undoManager) +{ + ValueTree v (state.getOrCreateChildWithName (stroke, undoManager)); + writeFillType (v, newFill, gp1, gp2, gp3, imageProvider, undoManager); +} - ColourGradient gradient; +const PathStrokeType DrawablePath::ValueTreeWrapper::getStrokeType() const +{ + const String jointStyleString (state [jointStyle].toString()); + const String capStyleString (state [capStyle].toString()); - addGradientStopsIn (gradient, inheritedFrom); - addGradientStopsIn (gradient, fillXml); + return PathStrokeType (state [strokeWidth], + jointStyleString == "curved" ? PathStrokeType::curved + : (jointStyleString == "bevel" ? PathStrokeType::beveled + : PathStrokeType::mitered), + capStyleString == "square" ? PathStrokeType::square + : (capStyleString == "round" ? PathStrokeType::rounded + : PathStrokeType::butt)); +} - if (gradient.getNumColours() > 0) - { - gradient.addColour (0.0, gradient.getColour (0)); - gradient.addColour (1.0, gradient.getColour (gradient.getNumColours() - 1)); - } - else - { - gradient.addColour (0.0, Colours::black); - gradient.addColour (1.0, Colours::black); - } +void DrawablePath::ValueTreeWrapper::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager) +{ + state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager); + state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered + ? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager); + state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt + ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); +} - if (overallOpacity.isNotEmpty()) - gradient.multiplyOpacity (overallOpacity.getFloatValue()); +bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const +{ + return state [nonZeroWinding]; +} - jassert (gradient.getNumColours() > 0); +void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) +{ + state.setProperty (nonZeroWinding, b, undoManager); +} - gradient.isRadial = fillXml->hasTagName ("radialGradient"); +const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode"); +const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); +const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); +const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); +const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); +const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); - float gradientWidth = viewBoxW; - float gradientHeight = viewBoxH; - float dx = 0.0f; - float dy = 0.0f; +const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner"; +const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round"; +const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm"; - const bool userSpace = fillXml->getStringAttribute ("gradientUnits").equalsIgnoreCase ("userSpaceOnUse"); +DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) + : state (state_) +{ +} - if (! userSpace) - { - const Rectangle bounds (path.getBounds()); - dx = bounds.getX(); - dy = bounds.getY(); - gradientWidth = bounds.getWidth(); - gradientHeight = bounds.getHeight(); - } +DrawablePath::ValueTreeWrapper::Element::~Element() +{ +} - if (gradient.isRadial) - { - gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("cx", "50%"), gradientWidth), - dy + getCoordLength (fillXml->getStringAttribute ("cy", "50%"), gradientHeight)); +DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const +{ + return ValueTreeWrapper (state.getParent().getParent()); +} - const float radius = getCoordLength (fillXml->getStringAttribute ("r", "50%"), gradientWidth); - gradient.point2 = gradient.point1 + Point (radius, 0.0f); +DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const +{ + return Element (state.getSibling (-1)); +} - //xxx (the fx, fy focal point isn't handled properly here..) - } - else - { - gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x1", "0%"), gradientWidth), - dy + getCoordLength (fillXml->getStringAttribute ("y1", "0%"), gradientHeight)); +int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const throw() +{ + const Identifier i (state.getType()); + if (i == startSubPathElement || i == lineToElement) return 1; + if (i == quadraticToElement) return 2; + if (i == cubicToElement) return 3; + return 0; +} - gradient.point2.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x2", "100%"), gradientWidth), - dy + getCoordLength (fillXml->getStringAttribute ("y2", "0%"), gradientHeight)); +const RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const +{ + jassert (index >= 0 && index < getNumControlPoints()); + return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); +} - if (gradient.point1 == gradient.point2) - return Colour (gradient.getColour (gradient.getNumColours() - 1)); - } +Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager) const +{ + jassert (index >= 0 && index < getNumControlPoints()); + return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager); +} - FillType type (gradient); - type.transform = parseTransform (fillXml->getStringAttribute ("gradientTransform")) - .followedBy (transform); - return type; - } - } +void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) +{ + jassert (index >= 0 && index < getNumControlPoints()); + return state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); +} - if (fill.equalsIgnoreCase ("none")) - return Colours::transparentBlack; +const RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const +{ + const Identifier i (state.getType()); - int i = 0; - const Colour colour (parseColour (fill, i, defaultColour)); - return colour.withMultipliedAlpha (opacity); - } + if (i == startSubPathElement) + return getControlPoint (0); - const PathStrokeType getStrokeFor (const XmlElement* const xml) const - { - const String strokeWidth (getStyleAttribute (xml, "stroke-width")); - const String cap (getStyleAttribute (xml, "stroke-linecap")); - const String join (getStyleAttribute (xml, "stroke-linejoin")); + jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement); - //const String mitreLimit (getStyleAttribute (xml, "stroke-miterlimit")); - //const String dashArray (getStyleAttribute (xml, "stroke-dasharray")); - //const String dashOffset (getStyleAttribute (xml, "stroke-dashoffset")); + return getPreviousElement().getEndPoint(); +} - PathStrokeType::JointStyle joinStyle = PathStrokeType::mitered; - PathStrokeType::EndCapStyle capStyle = PathStrokeType::butt; +const RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const +{ + const Identifier i (state.getType()); + if (i == startSubPathElement || i == lineToElement) return getControlPoint (0); + if (i == quadraticToElement) return getControlPoint (1); + if (i == cubicToElement) return getControlPoint (2); - if (join.equalsIgnoreCase ("round")) - joinStyle = PathStrokeType::curved; - else if (join.equalsIgnoreCase ("bevel")) - joinStyle = PathStrokeType::beveled; + jassert (i == closeSubPathElement); + return RelativePoint(); +} - if (cap.equalsIgnoreCase ("round")) - capStyle = PathStrokeType::rounded; - else if (cap.equalsIgnoreCase ("square")) - capStyle = PathStrokeType::square; +float DrawablePath::ValueTreeWrapper::Element::getLength (RelativeCoordinate::NamedCoordinateFinder* nameFinder) const +{ + const Identifier i (state.getType()); - float ox = 0.0f, oy = 0.0f; - float x = getCoordLength (strokeWidth, viewBoxW), y = 0.0f; - transform.transformPoints (ox, oy, x, y); + if (i == lineToElement || i == closeSubPathElement) + return getEndPoint().resolve (nameFinder).getDistanceFrom (getStartPoint().resolve (nameFinder)); - return PathStrokeType (strokeWidth.isNotEmpty() ? juce_hypotf (x - ox, y - oy) : 1.0f, - joinStyle, capStyle); + if (i == cubicToElement) + { + Path p; + p.startNewSubPath (getStartPoint().resolve (nameFinder)); + p.cubicTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder), getControlPoint (2).resolve (nameFinder)); + return p.getLength(); } - Drawable* parseText (const XmlElement& xml) + if (i == quadraticToElement) { - Array xCoords, yCoords, dxCoords, dyCoords; - - getCoordList (xCoords, getInheritedAttribute (&xml, "x"), true, true); - getCoordList (yCoords, getInheritedAttribute (&xml, "y"), true, false); - getCoordList (dxCoords, getInheritedAttribute (&xml, "dx"), true, true); - getCoordList (dyCoords, getInheritedAttribute (&xml, "dy"), true, false); + Path p; + p.startNewSubPath (getStartPoint().resolve (nameFinder)); + p.quadraticTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder)); + return p.getLength(); + } - //xxx not done text yet! + jassert (i == startSubPathElement); + return 0; +} - forEachXmlChildElement (xml, e) - { - if (e->isTextElement()) - { - const String text (e->getText()); +const String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const +{ + return state [mode].toString(); +} - Path path; - Drawable* s = parseShape (*e, path); - delete s; - } - else if (e->hasTagName ("tspan")) - { - Drawable* s = parseText (*e); - delete s; - } - } +void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager) +{ + if (state.hasType (cubicToElement)) + state.setProperty (mode, newMode, undoManager); +} - return 0; - } +void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager) +{ + const Identifier i (state.getType()); - void addTransform (const XmlElement& xml) + if (i == quadraticToElement || i == cubicToElement) { - transform = parseTransform (xml.getStringAttribute ("transform")) - .followedBy (transform); + ValueTree newState (lineToElement); + Element e (newState); + e.setControlPoint (0, getEndPoint(), undoManager); + state = newState; } +} - bool parseCoord (const String& s, float& value, int& index, - const bool allowUnits, const bool isX) const +void DrawablePath::ValueTreeWrapper::Element::convertToCubic (RelativeCoordinate::NamedCoordinateFinder* nameFinder, UndoManager* undoManager) +{ + const Identifier i (state.getType()); + + if (i == lineToElement || i == quadraticToElement) { - String number; + ValueTree newState (cubicToElement); + Element e (newState); - if (! parseNextNumber (s, number, index, allowUnits)) - { - value = 0; - return false; - } + const RelativePoint start (getStartPoint()); + const RelativePoint end (getEndPoint()); + const Point startResolved (start.resolve (nameFinder)); + const Point endResolved (end.resolve (nameFinder)); + e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager); + e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager); + e.setControlPoint (2, end, undoManager); - value = getCoordLength (number, isX ? viewBoxW : viewBoxH); - return true; + state = newState; } +} - bool parseCoords (const String& s, float& x, float& y, - int& index, const bool allowUnits) const - { - return parseCoord (s, x, index, allowUnits, true) - && parseCoord (s, y, index, allowUnits, false); - } +void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager) +{ + const Identifier i (state.getType()); - float getCoordLength (const String& s, const float sizeForProportions) const + if (i != startSubPathElement) { - float n = s.getFloatValue(); - const int len = s.length(); + ValueTree newState (startSubPathElement); + Element e (newState); + e.setControlPoint (0, getEndPoint(), undoManager); + state = newState; + } +} - if (len > 2) - { - const float dpi = 96.0f; +static const Point findCubicSubdivisionPoint (float proportion, const Point points[4]) +{ + const Point mid1 (points[0] + (points[1] - points[0]) * proportion), + mid2 (points[1] + (points[2] - points[1]) * proportion), + mid3 (points[2] + (points[3] - points[2]) * proportion); - const juce_wchar n1 = s [len - 2]; - const juce_wchar n2 = s [len - 1]; + const Point newCp1 (mid1 + (mid2 - mid1) * proportion), + newCp2 (mid2 + (mid3 - mid2) * proportion); - if (n1 == 'i' && n2 == 'n') - n *= dpi; - else if (n1 == 'm' && n2 == 'm') - n *= dpi / 25.4f; - else if (n1 == 'c' && n2 == 'm') - n *= dpi / 2.54f; - else if (n1 == 'p' && n2 == 'c') - n *= 15.0f; - else if (n2 == '%') - n *= 0.01f * sizeForProportions; - } + return newCp1 + (newCp2 - newCp1) * proportion; +} - return n; - } +static const Point findQuadraticSubdivisionPoint (float proportion, const Point points[3]) +{ + const Point mid1 (points[0] + (points[1] - points[0]) * proportion), + mid2 (points[1] + (points[2] - points[1]) * proportion); - void getCoordList (Array & coords, const String& list, - const bool allowUnits, const bool isX) const - { - int index = 0; - float value; + return mid1 + (mid2 - mid1) * proportion; +} - while (parseCoord (list, value, index, allowUnits, isX)) - coords.add (value); - } +float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (const Point& targetPoint, RelativeCoordinate::NamedCoordinateFinder* nameFinder) const +{ + const Identifier i (state.getType()); + float bestProp = 0; - void parseCSSStyle (const XmlElement& xml) + if (i == cubicToElement) { - cssStyleText = xml.getAllSubText() + "\n" + cssStyleText; - } + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); - const String getStyleAttribute (const XmlElement* xml, const String& attributeName, - const String& defaultValue = String::empty) const - { - if (xml->hasAttribute (attributeName)) - return xml->getStringAttribute (attributeName, defaultValue); + const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) }; - const String styleAtt (xml->getStringAttribute ("style")); + float bestDistance = std::numeric_limits::max(); - if (styleAtt.isNotEmpty()) + for (int i = 110; --i >= 0;) { - const String value (getAttributeFromStyleList (styleAtt, attributeName, String::empty)); + float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); + const Point centre (findCubicSubdivisionPoint (prop, points)); + const float distance = centre.getDistanceFrom (targetPoint); - if (value.isNotEmpty()) - return value; + if (distance < bestDistance) + { + bestProp = prop; + bestDistance = distance; + } } - else if (xml->hasAttribute ("class")) - { - const String className ("." + xml->getStringAttribute ("class")); + } + else if (i == quadraticToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); + const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) }; - int index = cssStyleText.indexOfIgnoreCase (className + " "); + float bestDistance = std::numeric_limits::max(); - if (index < 0) - index = cssStyleText.indexOfIgnoreCase (className + "{"); + for (int i = 110; --i >= 0;) + { + float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); + const Point centre (findQuadraticSubdivisionPoint ((float) prop, points)); + const float distance = centre.getDistanceFrom (targetPoint); - if (index >= 0) + if (distance < bestDistance) { - const int openBracket = cssStyleText.indexOfChar (index, '{'); - - if (openBracket > index) - { - const int closeBracket = cssStyleText.indexOfChar (openBracket, '}'); - - if (closeBracket > openBracket) - { - const String value (getAttributeFromStyleList (cssStyleText.substring (openBracket + 1, closeBracket), attributeName, defaultValue)); - - if (value.isNotEmpty()) - return value; - } - } + bestProp = prop; + bestDistance = distance; } } + } + else if (i == lineToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); + const Line line (rp1.resolve (nameFinder), rp2.resolve (nameFinder)); + bestProp = line.findNearestProportionalPositionTo (targetPoint); + } - xml = const_cast (topLevelXml)->findParentElementOf (xml); - - if (xml != 0) - return getStyleAttribute (xml, attributeName, defaultValue); + return bestProp; +} - return defaultValue; - } +ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (const Point& targetPoint, RelativeCoordinate::NamedCoordinateFinder* nameFinder, UndoManager* undoManager) +{ + ValueTree newTree; + const Identifier i (state.getType()); - const String getInheritedAttribute (const XmlElement* xml, const String& attributeName) const + if (i == cubicToElement) { - if (xml->hasAttribute (attributeName)) - return xml->getStringAttribute (attributeName); + float bestProp = findProportionAlongLine (targetPoint, nameFinder); - xml = const_cast (topLevelXml)->findParentElementOf (xml); + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); + const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) }; - if (xml != 0) - return getInheritedAttribute (xml, attributeName); + const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), + mid2 (points[1] + (points[2] - points[1]) * bestProp), + mid3 (points[2] + (points[3] - points[2]) * bestProp); - return String::empty; - } + const Point newCp1 (mid1 + (mid2 - mid1) * bestProp), + newCp2 (mid2 + (mid3 - mid2) * bestProp); - static bool isIdentifierChar (const juce_wchar c) - { - return CharacterFunctions::isLetter (c) || c == '-'; - } + const Point newCentre (newCp1 + (newCp2 - newCp1) * bestProp); - static const String getAttributeFromStyleList (const String& list, const String& attributeName, const String& defaultValue) + setControlPoint (0, mid1, undoManager); + setControlPoint (1, newCp1, undoManager); + setControlPoint (2, newCentre, undoManager); + setModeOfEndPoint (roundedMode, undoManager); + + Element newElement (newTree = ValueTree (cubicToElement)); + newElement.setControlPoint (0, newCp2, 0); + newElement.setControlPoint (1, mid3, 0); + newElement.setControlPoint (2, rp4, 0); + + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); + } + else if (i == quadraticToElement) { - int i = 0; + float bestProp = findProportionAlongLine (targetPoint, nameFinder); - for (;;) - { - i = list.indexOf (i, attributeName); + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); + const Point points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) }; - if (i < 0) - break; + const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), + mid2 (points[1] + (points[2] - points[1]) * bestProp); - if ((i == 0 || (i > 0 && ! isIdentifierChar (list [i - 1]))) - && ! isIdentifierChar (list [i + attributeName.length()])) - { - i = list.indexOfChar (i, ':'); + const Point newCentre (mid1 + (mid2 - mid1) * bestProp); - if (i < 0) - break; + setControlPoint (0, mid1, undoManager); + setControlPoint (1, newCentre, undoManager); + setModeOfEndPoint (roundedMode, undoManager); - int end = list.indexOfChar (i, ';'); + Element newElement (newTree = ValueTree (quadraticToElement)); + newElement.setControlPoint (0, mid2, 0); + newElement.setControlPoint (1, rp3, 0); - if (end < 0) - end = 0x7ffff; + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); + } + else if (i == lineToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); + const Line line (rp1.resolve (nameFinder), rp2.resolve (nameFinder)); + const Point newPoint (line.findNearestPointTo (targetPoint)); - return list.substring (i + 1, end).trim(); - } + setControlPoint (0, newPoint, undoManager); - ++i; - } + Element newElement (newTree = ValueTree (lineToElement)); + newElement.setControlPoint (0, rp2, 0); - return defaultValue; + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); } - - static bool parseNextNumber (const String& source, String& value, int& index, const bool allowUnits) + else if (i == closeSubPathElement) { - const juce_wchar* const s = source; + } - while (CharacterFunctions::isWhitespace (s[index]) || s[index] == ',') - ++index; + return newTree; +} - int start = index; +void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager) +{ + state.getParent().removeChild (state, undoManager); +} - if (CharacterFunctions::isDigit (s[index]) || s[index] == '.' || s[index] == '-') - ++index; +const Rectangle DrawablePath::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) +{ + Rectangle damageRect; + ValueTreeWrapper v (tree); + setName (v.getID()); - while (CharacterFunctions::isDigit (s[index]) || s[index] == '.') - ++index; + bool needsRedraw = false; + const FillType newFill (v.getMainFill (parent, imageProvider)); - if ((s[index] == 'e' || s[index] == 'E') - && (CharacterFunctions::isDigit (s[index + 1]) - || s[index + 1] == '-' - || s[index + 1] == '+')) - { - index += 2; + if (mainFill != newFill) + { + needsRedraw = true; + mainFill = newFill; + } - while (CharacterFunctions::isDigit (s[index])) - ++index; - } + const FillType newStrokeFill (v.getStrokeFill (parent, imageProvider)); - if (allowUnits) - { - while (CharacterFunctions::isLetter (s[index])) - ++index; - } + if (strokeFill != newStrokeFill) + { + needsRedraw = true; + strokeFill = newStrokeFill; + } - if (index == start) - return false; + const PathStrokeType newStroke (v.getStrokeType()); - value = String (s + start, index - start); + ScopedPointer newRelativePath (new RelativePointPath (tree)); - while (CharacterFunctions::isWhitespace (s[index]) || s[index] == ',') - ++index; + Path newPath; + newRelativePath->createPath (newPath, parent); - return true; - } + if (! newRelativePath->containsAnyDynamicPoints()) + newRelativePath = 0; - static const Colour parseColour (const String& s, int& index, const Colour& defaultColour) + if (strokeType != newStroke || path != newPath) { - if (s [index] == '#') - { - uint32 hex [6]; - zeromem (hex, sizeof (hex)); - int numChars = 0; - - for (int i = 6; --i >= 0;) - { - const int hexValue = CharacterFunctions::getHexDigitValue (s [++index]); + damageRect = getBounds(); + path.swapWithPath (newPath); + strokeNeedsUpdating = true; + strokeType = newStroke; + needsRedraw = true; + } - if (hexValue >= 0) - hex [numChars++] = hexValue; - else - break; - } + relativePath = newRelativePath; - if (numChars <= 3) - return Colour ((uint8) (hex [0] * 0x11), - (uint8) (hex [1] * 0x11), - (uint8) (hex [2] * 0x11)); - else - return Colour ((uint8) ((hex [0] << 4) + hex [1]), - (uint8) ((hex [2] << 4) + hex [3]), - (uint8) ((hex [4] << 4) + hex [5])); - } - else if (s [index] == 'r' - && s [index + 1] == 'g' - && s [index + 2] == 'b') - { - const int openBracket = s.indexOfChar (index, '('); - const int closeBracket = s.indexOfChar (openBracket, ')'); + if (needsRedraw) + damageRect = damageRect.getUnion (getBounds()); - if (openBracket >= 3 && closeBracket > openBracket) - { - index = closeBracket; + return damageRect; +} - StringArray tokens; - tokens.addTokens (s.substring (openBracket + 1, closeBracket), ",", ""); - tokens.trim(); - tokens.removeEmptyStrings(); +const ValueTree DrawablePath::createValueTree (ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); - if (tokens[0].containsChar ('%')) - return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()), - (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()), - (uint8) roundToInt (2.55 * tokens[2].getDoubleValue())); - else - return Colour ((uint8) tokens[0].getIntValue(), - (uint8) tokens[1].getIntValue(), - (uint8) tokens[2].getIntValue()); - } - } + v.setID (getName(), 0); + v.setMainFill (mainFill, 0, 0, 0, imageProvider, 0); + v.setStrokeFill (strokeFill, 0, 0, 0, imageProvider, 0); + v.setStrokeType (strokeType, 0); - return Colours::findColourForName (s, defaultColour); + if (relativePath != 0) + { + relativePath->writeTo (tree, 0); + } + else + { + RelativePointPath rp (path); + rp.writeTo (tree, 0); } - static const AffineTransform parseTransform (String t) - { - AffineTransform result; + return tree; +} - while (t.isNotEmpty()) - { - StringArray tokens; - tokens.addTokens (t.fromFirstOccurrenceOf ("(", false, false) - .upToFirstOccurrenceOf (")", false, false), - ", ", String::empty); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_DrawablePath.cpp ***/ - tokens.removeEmptyStrings (true); - float numbers [6]; +/*** Start of inlined file: juce_DrawableText.cpp ***/ +BEGIN_JUCE_NAMESPACE - for (int i = 0; i < 6; ++i) - numbers[i] = tokens[i].getFloatValue(); +DrawableText::DrawableText() + : colour (Colours::black), + justification (Justification::centredLeft) +{ + setFont (Font (15.0f), true); +} - AffineTransform trans; +DrawableText::DrawableText (const DrawableText& other) + : text (other.text), + font (other.font), + colour (other.colour), + justification (other.justification), + bounds (other.bounds), + fontSizeControlPoint (other.fontSizeControlPoint) +{ +} - if (t.startsWithIgnoreCase ("matrix")) - { - trans = AffineTransform (numbers[0], numbers[2], numbers[4], - numbers[1], numbers[3], numbers[5]); - } - else if (t.startsWithIgnoreCase ("translate")) - { - jassert (tokens.size() == 2); - trans = AffineTransform::translation (numbers[0], numbers[1]); - } - else if (t.startsWithIgnoreCase ("scale")) - { - if (tokens.size() == 1) - trans = AffineTransform::scale (numbers[0], numbers[0]); - else - trans = AffineTransform::scale (numbers[0], numbers[1]); - } - else if (t.startsWithIgnoreCase ("rotate")) - { - if (tokens.size() != 3) - trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi)); - else - trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi), - numbers[1], numbers[2]); - } - else if (t.startsWithIgnoreCase ("skewX")) - { - trans = AffineTransform (1.0f, std::tan (numbers[0] * (float_Pi / 180.0f)), 0.0f, - 0.0f, 1.0f, 0.0f); - } - else if (t.startsWithIgnoreCase ("skewY")) - { - trans = AffineTransform (1.0f, 0.0f, 0.0f, - std::tan (numbers[0] * (float_Pi / 180.0f)), 1.0f, 0.0f); - } +DrawableText::~DrawableText() +{ +} - result = trans.followedBy (result); - t = t.fromFirstOccurrenceOf (")", false, false).trimStart(); - } +void DrawableText::setText (const String& newText) +{ + text = newText; +} - return result; - } +void DrawableText::setColour (const Colour& newColour) +{ + colour = newColour; +} - static void endpointToCentreParameters (const double x1, const double y1, - const double x2, const double y2, - const double angle, - const bool largeArc, const bool sweep, - double& rx, double& ry, - double& centreX, double& centreY, - double& startAngle, double& deltaAngle) +void DrawableText::setFont (const Font& newFont, bool applySizeAndScale) +{ + font = newFont; + + if (applySizeAndScale) { - const double midX = (x1 - x2) * 0.5; - const double midY = (y1 - y2) * 0.5; + Point corners[3]; + bounds.resolveThreePoints (corners, parent); - const double cosAngle = cos (angle); - const double sinAngle = sin (angle); - const double xp = cosAngle * midX + sinAngle * midY; - const double yp = cosAngle * midY - sinAngle * midX; - const double xp2 = xp * xp; - const double yp2 = yp * yp; + setFontSizeControlPoint (RelativePoint (RelativeParallelogram::getPointForInternalCoord (corners, + Point (font.getHorizontalScale() * font.getHeight(), font.getHeight())))); + } +} - double rx2 = rx * rx; - double ry2 = ry * ry; +void DrawableText::setJustification (const Justification& newJustification) +{ + justification = newJustification; +} - const double s = (xp2 / rx2) + (yp2 / ry2); - double c; +void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) +{ + bounds = newBounds; +} - if (s <= 1.0) - { - c = std::sqrt (jmax (0.0, ((rx2 * ry2) - (rx2 * yp2) - (ry2 * xp2)) - / (( rx2 * yp2) + (ry2 * xp2)))); +void DrawableText::setFontSizeControlPoint (const RelativePoint& newPoint) +{ + fontSizeControlPoint = newPoint; +} - if (largeArc == sweep) - c = -c; - } - else - { - const double s2 = std::sqrt (s); - rx *= s2; - ry *= s2; - rx2 = rx * rx; - ry2 = ry * ry; - c = 0; - } +void DrawableText::render (const Drawable::RenderingContext& context) const +{ + Point points[3]; + bounds.resolveThreePoints (points, parent); - const double cpx = ((rx * yp) / ry) * c; - const double cpy = ((-ry * xp) / rx) * c; + const float w = Line (points[0], points[1]).getLength(); + const float h = Line (points[0], points[2]).getLength(); - centreX = ((x1 + x2) * 0.5) + (cosAngle * cpx) - (sinAngle * cpy); - centreY = ((y1 + y2) * 0.5) + (sinAngle * cpx) + (cosAngle * cpy); + const Point fontCoords (bounds.getInternalCoordForPoint (points, fontSizeControlPoint.resolve (parent))); + const float fontHeight = jlimit (1.0f, h, fontCoords.getY()); + const float fontWidth = jlimit (0.01f, w, fontCoords.getX()); - const double ux = (xp - cpx) / rx; - const double uy = (yp - cpy) / ry; - const double vx = (-xp - cpx) / rx; - const double vy = (-yp - cpy) / ry; + Font f (font); + f.setHeight (fontHeight); + f.setHorizontalScale (fontWidth / fontHeight); - const double length = juce_hypot (ux, uy); + context.g.setColour (colour.withMultipliedAlpha (context.opacity)); - startAngle = acos (jlimit (-1.0, 1.0, ux / length)); + GlyphArrangement ga; + ga.addFittedText (f, text, 0, 0, w, h, justification, 0x100000); + ga.draw (context.g, + AffineTransform::fromTargetPoints (0, 0, points[0].getX(), points[0].getY(), + w, 0, points[1].getX(), points[1].getY(), + 0, h, points[2].getX(), points[2].getY()) + .followedBy (context.transform)); +} - if (uy < 0) - startAngle = -startAngle; +const Rectangle DrawableText::getBounds() const +{ + return bounds.getBounds (parent); +} - startAngle += double_Pi * 0.5; +bool DrawableText::hitTest (float x, float y) const +{ + Path p; + bounds.getPath (p, parent); + return p.contains (x, y); +} - deltaAngle = acos (jlimit (-1.0, 1.0, ((ux * vx) + (uy * vy)) - / (length * juce_hypot (vx, vy)))); +Drawable* DrawableText::createCopy() const +{ + return new DrawableText (*this); +} - if ((ux * vy) - (uy * vx) < 0) - deltaAngle = -deltaAngle; +void DrawableText::invalidatePoints() +{ +} - if (sweep) - { - if (deltaAngle < 0) - deltaAngle += double_Pi * 2.0; - } - else - { - if (deltaAngle > 0) - deltaAngle -= double_Pi * 2.0; - } +const Identifier DrawableText::valueTreeType ("Text"); - deltaAngle = fmod (deltaAngle, double_Pi * 2.0); - } +const Identifier DrawableText::ValueTreeWrapper::text ("text"); +const Identifier DrawableText::ValueTreeWrapper::colour ("colour"); +const Identifier DrawableText::ValueTreeWrapper::font ("font"); +const Identifier DrawableText::ValueTreeWrapper::justification ("justification"); +const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft"); +const Identifier DrawableText::ValueTreeWrapper::fontSizeAnchor ("fontSizeAnchor"); - static const XmlElement* findElementForId (const XmlElement* const parent, const String& id) - { - forEachXmlChildElement (*parent, e) - { - if (e->compareAttribute ("id", id)) - return e; +DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} - const XmlElement* const found = findElementForId (e, id); +const String DrawableText::ValueTreeWrapper::getText() const +{ + return state [text].toString(); +} - if (found != 0) - return found; - } +void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) +{ + state.setProperty (text, newText, undoManager); +} - return 0; - } +Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (text, undoManager); +} - SVGState& operator= (const SVGState&); -}; +const Colour DrawableText::ValueTreeWrapper::getColour() const +{ + return Colour::fromString (state [colour].toString()); +} -Drawable* Drawable::createFromSVG (const XmlElement& svgDocument) +void DrawableText::ValueTreeWrapper::setColour (const Colour& newColour, UndoManager* undoManager) { - SVGState state (&svgDocument); - return state.parseSVGElement (svgDocument); + state.setProperty (colour, newColour.toString(), undoManager); } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_SVGParser.cpp ***/ +const Justification DrawableText::ValueTreeWrapper::getJustification() const +{ + return Justification ((int) state [justification]); +} +void DrawableText::ValueTreeWrapper::setJustification (const Justification& newJustification, UndoManager* undoManager) +{ + state.setProperty (justification, newJustification.getFlags(), undoManager); +} -/*** Start of inlined file: juce_DropShadowEffect.cpp ***/ -BEGIN_JUCE_NAMESPACE +const Font DrawableText::ValueTreeWrapper::getFont() const +{ + return Font::fromString (state [font]); +} -#if JUCE_MSVC && JUCE_DEBUG - #pragma optimize ("t", on) -#endif +void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager) +{ + state.setProperty (font, newFont.toString(), undoManager); +} -DropShadowEffect::DropShadowEffect() - : offsetX (0), - offsetY (0), - radius (4), - opacity (0.6f) +Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager) { + return state.getPropertyAsValue (font, undoManager); } -DropShadowEffect::~DropShadowEffect() +const RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const { + return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString()); } -void DropShadowEffect::setShadowProperties (const float newRadius, - const float newOpacity, - const int newShadowOffsetX, - const int newShadowOffsetY) +void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) { - radius = jmax (1.1f, newRadius); - offsetX = newShadowOffsetX; - offsetY = newShadowOffsetY; - opacity = newOpacity; + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); } -void DropShadowEffect::applyEffect (Image& image, Graphics& g) +const RelativePoint DrawableText::ValueTreeWrapper::getFontSizeControlPoint() const { - const int w = image.getWidth(); - const int h = image.getHeight(); + return state [fontSizeAnchor].toString(); +} - Image shadowImage (Image::SingleChannel, w, h, false); +void DrawableText::ValueTreeWrapper::setFontSizeControlPoint (const RelativePoint& p, UndoManager* undoManager) +{ + state.setProperty (fontSizeAnchor, p.toString(), undoManager); +} - const Image::BitmapData srcData (image, 0, 0, w, h); - const Image::BitmapData destData (shadowImage, 0, 0, w, h, true); +const Rectangle DrawableText::refreshFromValueTree (const ValueTree& tree, ImageProvider*) +{ + ValueTreeWrapper v (tree); + setName (v.getID()); - const int filter = roundToInt (63.0f / radius); - const int radiusMinus1 = roundToInt ((radius - 1.0f) * 63.0f); + const RelativeParallelogram newBounds (v.getBoundingBox()); + const RelativePoint newFontPoint (v.getFontSizeControlPoint()); + const Colour newColour (v.getColour()); + const Justification newJustification (v.getJustification()); + const String newText (v.getText()); + const Font newFont (v.getFont()); - for (int x = w; --x >= 0;) + if (text != newText || font != newFont || justification != newJustification + || colour != newColour || bounds != newBounds || newFontPoint != fontSizeControlPoint) { - int shadowAlpha = 0; + const Rectangle damage (getBounds()); - const PixelARGB* src = ((const PixelARGB*) srcData.data) + x; - uint8* shadowPix = destData.data + x; + setBoundingBox (newBounds); + setFontSizeControlPoint (newFontPoint); + setColour (newColour); + setFont (newFont, false); + setJustification (newJustification); + setText (newText); - for (int y = h; --y >= 0;) - { - shadowAlpha = ((shadowAlpha * radiusMinus1 + (src->getAlpha() << 6)) * filter) >> 12; + return damage.getUnion (getBounds()); - *shadowPix = (uint8) shadowAlpha; - src = (const PixelARGB*) (((const uint8*) src) + srcData.lineStride); - shadowPix += destData.lineStride; - } } - for (int y = h; --y >= 0;) - { - int shadowAlpha = 0; - uint8* shadowPix = destData.getLinePointer (y); + return Rectangle(); +} - for (int x = w; --x >= 0;) - { - shadowAlpha = ((shadowAlpha * radiusMinus1 + (*shadowPix << 6)) * filter) >> 12; - *shadowPix++ = (uint8) shadowAlpha; - } - } +const ValueTree DrawableText::createValueTree (ImageProvider*) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); - g.setColour (Colours::black.withAlpha (opacity)); - g.drawImageAt (shadowImage, offsetX, offsetY, true); + v.setID (getName(), 0); + v.setText (text, 0); + v.setFont (font, 0); + v.setJustification (justification, 0); + v.setColour (colour, 0); + v.setBoundingBox (bounds, 0); + v.setFontSizeControlPoint (fontSizeControlPoint, 0); - g.setOpacity (1.0f); - g.drawImageAt (image, 0, 0); + return tree; } -#if JUCE_MSVC && JUCE_DEBUG - #pragma optimize ("", on) // resets optimisations to the project defaults -#endif - END_JUCE_NAMESPACE -/*** End of inlined file: juce_DropShadowEffect.cpp ***/ +/*** End of inlined file: juce_DrawableText.cpp ***/ -/*** Start of inlined file: juce_GlowEffect.cpp ***/ +/*** Start of inlined file: juce_SVGParser.cpp ***/ BEGIN_JUCE_NAMESPACE -GlowEffect::GlowEffect() - : radius (2.0f), - colour (Colours::white) +class SVGState { -} +public: -GlowEffect::~GlowEffect() -{ -} + SVGState (const XmlElement* const topLevel) + : topLevelXml (topLevel), + elementX (0), elementY (0), + width (512), height (512), + viewBoxW (0), viewBoxH (0) + { + } -void GlowEffect::setGlowProperties (const float newRadius, - const Colour& newColour) -{ - radius = newRadius; - colour = newColour; -} + ~SVGState() + { + } -void GlowEffect::applyEffect (Image& image, Graphics& g) -{ - Image temp (image.getFormat(), image.getWidth(), image.getHeight(), true); + Drawable* parseSVGElement (const XmlElement& xml) + { + if (! xml.hasTagName ("svg")) + return 0; - ImageConvolutionKernel blurKernel (roundToInt (radius * 2.0f)); + DrawableComposite* const drawable = new DrawableComposite(); - blurKernel.createGaussianBlur (radius); - blurKernel.rescaleAllValues (radius); + drawable->setName (xml.getStringAttribute ("id")); - blurKernel.applyToImage (temp, image, image.getBounds()); + SVGState newState (*this); - g.setColour (colour); - g.drawImageAt (temp, 0, 0, true); + if (xml.hasAttribute ("transform")) + newState.addTransform (xml); - g.setOpacity (1.0f); - g.drawImageAt (image, 0, 0, false); -} + newState.elementX = getCoordLength (xml.getStringAttribute ("x", String (newState.elementX)), viewBoxW); + newState.elementY = getCoordLength (xml.getStringAttribute ("y", String (newState.elementY)), viewBoxH); + newState.width = getCoordLength (xml.getStringAttribute ("width", String (newState.width)), viewBoxW); + newState.height = getCoordLength (xml.getStringAttribute ("height", String (newState.height)), viewBoxH); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_GlowEffect.cpp ***/ + if (xml.hasAttribute ("viewBox")) + { + const String viewParams (xml.getStringAttribute ("viewBox")); + int i = 0; + float vx, vy, vw, vh; + + if (parseCoords (viewParams, vx, vy, i, true) + && parseCoords (viewParams, vw, vh, i, true) + && vw > 0 + && vh > 0) + { + newState.viewBoxW = vw; + newState.viewBoxH = vh; + + int placementFlags = 0; + + const String aspect (xml.getStringAttribute ("preserveAspectRatio")); + + if (aspect.containsIgnoreCase ("none")) + { + placementFlags = RectanglePlacement::stretchToFit; + } + else + { + if (aspect.containsIgnoreCase ("slice")) + placementFlags |= RectanglePlacement::fillDestination; + + if (aspect.containsIgnoreCase ("xMin")) + placementFlags |= RectanglePlacement::xLeft; + else if (aspect.containsIgnoreCase ("xMax")) + placementFlags |= RectanglePlacement::xRight; + else + placementFlags |= RectanglePlacement::xMid; + + if (aspect.containsIgnoreCase ("yMin")) + placementFlags |= RectanglePlacement::yTop; + else if (aspect.containsIgnoreCase ("yMax")) + placementFlags |= RectanglePlacement::yBottom; + else + placementFlags |= RectanglePlacement::yMid; + } + + const RectanglePlacement placement (placementFlags); + + newState.transform + = placement.getTransformToFit (vx, vy, vw, vh, + 0.0f, 0.0f, newState.width, newState.height) + .followedBy (newState.transform); + } + } + else + { + if (viewBoxW == 0) + newState.viewBoxW = newState.width; + + if (viewBoxH == 0) + newState.viewBoxH = newState.height; + } + + newState.parseSubElements (xml, drawable); + + drawable->resetContentAreaAndBoundingBoxToFitChildren(); + return drawable; + } + +private: + + const XmlElement* const topLevelXml; + float elementX, elementY, width, height, viewBoxW, viewBoxH; + AffineTransform transform; + String cssStyleText; + void parseSubElements (const XmlElement& xml, DrawableComposite* const parentDrawable) + { + forEachXmlChildElement (xml, e) + { + Drawable* d = 0; -/*** Start of inlined file: juce_ReduceOpacityEffect.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (e->hasTagName ("g")) d = parseGroupElement (*e); + else if (e->hasTagName ("svg")) d = parseSVGElement (*e); + else if (e->hasTagName ("path")) d = parsePath (*e); + else if (e->hasTagName ("rect")) d = parseRect (*e); + else if (e->hasTagName ("circle")) d = parseCircle (*e); + else if (e->hasTagName ("ellipse")) d = parseEllipse (*e); + else if (e->hasTagName ("line")) d = parseLine (*e); + else if (e->hasTagName ("polyline")) d = parsePolygon (*e, true); + else if (e->hasTagName ("polygon")) d = parsePolygon (*e, false); + else if (e->hasTagName ("text")) d = parseText (*e); + else if (e->hasTagName ("switch")) d = parseSwitch (*e); + else if (e->hasTagName ("style")) parseCSSStyle (*e); -ReduceOpacityEffect::ReduceOpacityEffect (const float opacity_) - : opacity (opacity_) -{ -} + parentDrawable->insertDrawable (d); + } + } -ReduceOpacityEffect::~ReduceOpacityEffect() -{ -} + DrawableComposite* parseSwitch (const XmlElement& xml) + { + const XmlElement* const group = xml.getChildByName ("g"); -void ReduceOpacityEffect::setOpacity (const float newOpacity) -{ - opacity = jlimit (0.0f, 1.0f, newOpacity); -} + if (group != 0) + return parseGroupElement (*group); -void ReduceOpacityEffect::applyEffect (Image& image, Graphics& g) -{ - g.setOpacity (opacity); - g.drawImageAt (image, 0, 0); -} + return 0; + } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_ReduceOpacityEffect.cpp ***/ + DrawableComposite* parseGroupElement (const XmlElement& xml) + { + DrawableComposite* const drawable = new DrawableComposite(); + drawable->setName (xml.getStringAttribute ("id")); -/*** Start of inlined file: juce_Font.cpp ***/ -BEGIN_JUCE_NAMESPACE + if (xml.hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); -namespace FontValues -{ - static float limitFontHeight (const float height) throw() - { - return jlimit (0.1f, 10000.0f, height); - } + newState.parseSubElements (xml, drawable); + } + else + { + parseSubElements (xml, drawable); + } - static const float defaultFontHeight = 14.0f; + drawable->resetContentAreaAndBoundingBoxToFitChildren(); + return drawable; + } - static String fallbackFont; -} + Drawable* parsePath (const XmlElement& xml) const + { + const String d (xml.getStringAttribute ("d").trimStart()); + Path path; -Font::SharedFontInternal::SharedFontInternal (const String& typefaceName_, const float height_, const float horizontalScale_, - const float kerning_, const float ascent_, const int styleFlags_, - Typeface* const typeface_) throw() - : typefaceName (typefaceName_), - height (height_), - horizontalScale (horizontalScale_), - kerning (kerning_), - ascent (ascent_), - styleFlags (styleFlags_), - typeface (typeface_) -{ -} + if (getStyleAttribute (&xml, "fill-rule").trim().equalsIgnoreCase ("evenodd")) + path.setUsingNonZeroWinding (false); -Font::SharedFontInternal::SharedFontInternal (const SharedFontInternal& other) throw() - : typefaceName (other.typefaceName), - height (other.height), - horizontalScale (other.horizontalScale), - kerning (other.kerning), - ascent (other.ascent), - styleFlags (other.styleFlags), - typeface (other.typeface) -{ -} + int index = 0; + float lastX = 0, lastY = 0; + float lastX2 = 0, lastY2 = 0; + juce_wchar lastCommandChar = 0; + bool isRelative = true; + bool carryOn = true; -Font::Font() throw() - : font (new SharedFontInternal (getDefaultSansSerifFontName(), FontValues::defaultFontHeight, - 1.0f, 0, 0, Font::plain, 0)) -{ -} + const String validCommandChars ("MmLlHhVvCcSsQqTtAaZz"); -Font::Font (const float fontHeight, const int styleFlags_) throw() - : font (new SharedFontInternal (getDefaultSansSerifFontName(), FontValues::limitFontHeight (fontHeight), - 1.0f, 0, 0, styleFlags_, 0)) -{ -} + while (d[index] != 0) + { + float x, y, x2, y2, x3, y3; -Font::Font (const String& typefaceName_, - const float fontHeight, - const int styleFlags_) throw() - : font (new SharedFontInternal (typefaceName_, FontValues::limitFontHeight (fontHeight), - 1.0f, 0, 0, styleFlags_, 0)) -{ -} + if (validCommandChars.containsChar (d[index])) + { + lastCommandChar = d [index++]; + isRelative = (lastCommandChar >= 'a' && lastCommandChar <= 'z'); + } -Font::Font (const Font& other) throw() - : font (other.font) -{ -} + switch (lastCommandChar) + { + case 'M': + case 'm': + case 'L': + case 'l': + if (parseCoords (d, x, y, index, false)) + { + if (isRelative) + { + x += lastX; + y += lastY; + } -Font& Font::operator= (const Font& other) throw() -{ - font = other.font; - return *this; -} + if (lastCommandChar == 'M' || lastCommandChar == 'm') + { + path.startNewSubPath (x, y); + lastCommandChar = 'l'; + } + else + path.lineTo (x, y); -Font::~Font() throw() -{ -} + lastX2 = lastX; + lastY2 = lastY; + lastX = x; + lastY = y; + } + else + { + ++index; + } -Font::Font (const Typeface::Ptr& typeface) throw() - : font (new SharedFontInternal (typeface->getName(), FontValues::defaultFontHeight, - 1.0f, 0, 0, Font::plain, typeface)) -{ -} + break; -bool Font::operator== (const Font& other) const throw() -{ - return font == other.font - || (font->height == other.font->height - && font->styleFlags == other.font->styleFlags - && font->horizontalScale == other.font->horizontalScale - && font->kerning == other.font->kerning - && font->typefaceName == other.font->typefaceName); -} + case 'H': + case 'h': + if (parseCoord (d, x, index, false, true)) + { + if (isRelative) + x += lastX; -bool Font::operator!= (const Font& other) const throw() -{ - return ! operator== (other); -} + path.lineTo (x, lastY); -void Font::dupeInternalIfShared() throw() -{ - if (font->getReferenceCount() > 1) - font = new SharedFontInternal (*font); -} + lastX2 = lastX; + lastX = x; + } + else + { + ++index; + } + break; -const String Font::getDefaultSansSerifFontName() throw() -{ - static const String name (""); - return name; -} + case 'V': + case 'v': + if (parseCoord (d, y, index, false, false)) + { + if (isRelative) + y += lastY; -const String Font::getDefaultSerifFontName() throw() -{ - static const String name (""); - return name; -} + path.lineTo (lastX, y); -const String Font::getDefaultMonospacedFontName() throw() -{ - static const String name (""); - return name; -} + lastY2 = lastY; + lastY = y; + } + else + { + ++index; + } + break; -void Font::setTypefaceName (const String& faceName) throw() -{ - if (faceName != font->typefaceName) - { - dupeInternalIfShared(); - font->typefaceName = faceName; - font->typeface = 0; - font->ascent = 0; - } -} + case 'C': + case 'c': + if (parseCoords (d, x, y, index, false) + && parseCoords (d, x2, y2, index, false) + && parseCoords (d, x3, y3, index, false)) + { + if (isRelative) + { + x += lastX; + y += lastY; + x2 += lastX; + y2 += lastY; + x3 += lastX; + y3 += lastY; + } -const String Font::getFallbackFontName() throw() -{ - return FontValues::fallbackFont; -} + path.cubicTo (x, y, x2, y2, x3, y3); -void Font::setFallbackFontName (const String& name) throw() -{ - FontValues::fallbackFont = name; -} + lastX2 = x2; + lastY2 = y2; + lastX = x3; + lastY = y3; + } + else + { + ++index; + } + break; -void Font::setHeight (float newHeight) throw() -{ - newHeight = FontValues::limitFontHeight (newHeight); + case 'S': + case 's': + if (parseCoords (d, x, y, index, false) + && parseCoords (d, x3, y3, index, false)) + { + if (isRelative) + { + x += lastX; + y += lastY; + x3 += lastX; + y3 += lastY; + } - if (font->height != newHeight) - { - dupeInternalIfShared(); - font->height = newHeight; - } -} + x2 = lastX + (lastX - lastX2); + y2 = lastY + (lastY - lastY2); + path.cubicTo (x2, y2, x, y, x3, y3); -void Font::setHeightWithoutChangingWidth (float newHeight) throw() -{ - newHeight = FontValues::limitFontHeight (newHeight); + lastX2 = x; + lastY2 = y; + lastX = x3; + lastY = y3; + } + else + { + ++index; + } + break; - if (font->height != newHeight) - { - dupeInternalIfShared(); - font->horizontalScale *= (font->height / newHeight); - font->height = newHeight; - } -} + case 'Q': + case 'q': + if (parseCoords (d, x, y, index, false) + && parseCoords (d, x2, y2, index, false)) + { + if (isRelative) + { + x += lastX; + y += lastY; + x2 += lastX; + y2 += lastY; + } -void Font::setStyleFlags (const int newFlags) throw() -{ - if (font->styleFlags != newFlags) - { - dupeInternalIfShared(); - font->styleFlags = newFlags; - font->typeface = 0; - font->ascent = 0; - } -} + path.quadraticTo (x, y, x2, y2); -void Font::setSizeAndStyle (float newHeight, - const int newStyleFlags, - const float newHorizontalScale, - const float newKerningAmount) throw() -{ - newHeight = FontValues::limitFontHeight (newHeight); + lastX2 = x; + lastY2 = y; + lastX = x2; + lastY = y2; + } + else + { + ++index; + } + break; - if (font->height != newHeight - || font->horizontalScale != newHorizontalScale - || font->kerning != newKerningAmount) - { - dupeInternalIfShared(); - font->height = newHeight; - font->horizontalScale = newHorizontalScale; - font->kerning = newKerningAmount; - } + case 'T': + case 't': + if (parseCoords (d, x, y, index, false)) + { + if (isRelative) + { + x += lastX; + y += lastY; + } - setStyleFlags (newStyleFlags); -} + x2 = lastX + (lastX - lastX2); + y2 = lastY + (lastY - lastY2); + path.quadraticTo (x2, y2, x, y); -void Font::setHorizontalScale (const float scaleFactor) throw() -{ - dupeInternalIfShared(); - font->horizontalScale = scaleFactor; -} + lastX2 = x2; + lastY2 = y2; + lastX = x; + lastY = y; + } + else + { + ++index; + } + break; -void Font::setExtraKerningFactor (const float extraKerning) throw() -{ - dupeInternalIfShared(); - font->kerning = extraKerning; -} + case 'A': + case 'a': + if (parseCoords (d, x, y, index, false)) + { + String num; -void Font::setBold (const bool shouldBeBold) throw() -{ - setStyleFlags (shouldBeBold ? (font->styleFlags | bold) - : (font->styleFlags & ~bold)); -} + if (parseNextNumber (d, num, index, false)) + { + const float angle = num.getFloatValue() * (180.0f / float_Pi); -bool Font::isBold() const throw() -{ - return (font->styleFlags & bold) != 0; -} + if (parseNextNumber (d, num, index, false)) + { + const bool largeArc = num.getIntValue() != 0; -void Font::setItalic (const bool shouldBeItalic) throw() -{ - setStyleFlags (shouldBeItalic ? (font->styleFlags | italic) - : (font->styleFlags & ~italic)); -} + if (parseNextNumber (d, num, index, false)) + { + const bool sweep = num.getIntValue() != 0; -bool Font::isItalic() const throw() -{ - return (font->styleFlags & italic) != 0; -} + if (parseCoords (d, x2, y2, index, false)) + { + if (isRelative) + { + x2 += lastX; + y2 += lastY; + } -void Font::setUnderline (const bool shouldBeUnderlined) throw() -{ - setStyleFlags (shouldBeUnderlined ? (font->styleFlags | underlined) - : (font->styleFlags & ~underlined)); -} + if (lastX != x2 || lastY != y2) + { + double centreX, centreY, startAngle, deltaAngle; + double rx = x, ry = y; -bool Font::isUnderlined() const throw() -{ - return (font->styleFlags & underlined) != 0; -} + endpointToCentreParameters (lastX, lastY, x2, y2, + angle, largeArc, sweep, + rx, ry, centreX, centreY, + startAngle, deltaAngle); -float Font::getAscent() const throw() -{ - if (font->ascent == 0) - font->ascent = getTypeface()->getAscent(); + path.addCentredArc ((float) centreX, (float) centreY, + (float) rx, (float) ry, + angle, (float) startAngle, (float) (startAngle + deltaAngle), + false); - return font->height * font->ascent; -} + path.lineTo (x2, y2); + } -float Font::getDescent() const throw() -{ - return font->height - getAscent(); -} + lastX2 = lastX; + lastY2 = lastY; + lastX = x2; + lastY = y2; + } + } + } + } + } + else + { + ++index; + } -int Font::getStringWidth (const String& text) const throw() -{ - return roundToInt (getStringWidthFloat (text)); -} + break; -float Font::getStringWidthFloat (const String& text) const throw() -{ - float w = getTypeface()->getStringWidth (text); + case 'Z': + case 'z': + path.closeSubPath(); + while (CharacterFunctions::isWhitespace (d [index])) + ++index; - if (font->kerning != 0) - w += font->kerning * text.length(); + break; - return w * font->height * font->horizontalScale; -} + default: + carryOn = false; + break; + } -void Font::getGlyphPositions (const String& text, Array & glyphs, Array & xOffsets) const throw() -{ - getTypeface()->getGlyphPositions (text, glyphs, xOffsets); + if (! carryOn) + break; + } - const float scale = font->height * font->horizontalScale; - const int num = xOffsets.size(); + return parseShape (xml, path); + } - if (num > 0) + Drawable* parseRect (const XmlElement& xml) const { - float* const x = &(xOffsets.getReference(0)); + Path rect; - if (font->kerning != 0) + const bool hasRX = xml.hasAttribute ("rx"); + const bool hasRY = xml.hasAttribute ("ry"); + + if (hasRX || hasRY) { - for (int i = 0; i < num; ++i) - x[i] = (x[i] + i * font->kerning) * scale; + float rx = getCoordLength (xml.getStringAttribute ("rx"), viewBoxW); + float ry = getCoordLength (xml.getStringAttribute ("ry"), viewBoxH); + + if (! hasRX) + rx = ry; + else if (! hasRY) + ry = rx; + + rect.addRoundedRectangle (getCoordLength (xml.getStringAttribute ("x"), viewBoxW), + getCoordLength (xml.getStringAttribute ("y"), viewBoxH), + getCoordLength (xml.getStringAttribute ("width"), viewBoxW), + getCoordLength (xml.getStringAttribute ("height"), viewBoxH), + rx, ry); } else { - for (int i = 0; i < num; ++i) - x[i] *= scale; + rect.addRectangle (getCoordLength (xml.getStringAttribute ("x"), viewBoxW), + getCoordLength (xml.getStringAttribute ("y"), viewBoxH), + getCoordLength (xml.getStringAttribute ("width"), viewBoxW), + getCoordLength (xml.getStringAttribute ("height"), viewBoxH)); } + + return parseShape (xml, rect); } -} -void Font::findFonts (Array& destArray) throw() -{ - const StringArray names (findAllTypefaceNames()); + Drawable* parseCircle (const XmlElement& xml) const + { + Path circle; - for (int i = 0; i < names.size(); ++i) - destArray.add (Font (names[i], FontValues::defaultFontHeight, Font::plain)); -} + const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); + const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); + const float radius = getCoordLength (xml.getStringAttribute ("r"), viewBoxW); -const String Font::toString() const -{ - String s (getTypefaceName()); + circle.addEllipse (cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); - if (s == getDefaultSansSerifFontName()) - s = String::empty; - else - s += "; "; + return parseShape (xml, circle); + } - s += String (getHeight(), 1); + Drawable* parseEllipse (const XmlElement& xml) const + { + Path ellipse; - if (isBold()) - s += " bold"; + const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); + const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); + const float radiusX = getCoordLength (xml.getStringAttribute ("rx"), viewBoxW); + const float radiusY = getCoordLength (xml.getStringAttribute ("ry"), viewBoxH); - if (isItalic()) - s += " italic"; + ellipse.addEllipse (cx - radiusX, cy - radiusY, radiusX * 2.0f, radiusY * 2.0f); - return s; -} + return parseShape (xml, ellipse); + } -const Font Font::fromString (const String& fontDescription) -{ - String name; + Drawable* parseLine (const XmlElement& xml) const + { + Path line; - const int separator = fontDescription.indexOfChar (';'); + const float x1 = getCoordLength (xml.getStringAttribute ("x1"), viewBoxW); + const float y1 = getCoordLength (xml.getStringAttribute ("y1"), viewBoxH); + const float x2 = getCoordLength (xml.getStringAttribute ("x2"), viewBoxW); + const float y2 = getCoordLength (xml.getStringAttribute ("y2"), viewBoxH); - if (separator > 0) - name = fontDescription.substring (0, separator).trim(); + line.startNewSubPath (x1, y1); + line.lineTo (x2, y2); - if (name.isEmpty()) - name = getDefaultSansSerifFontName(); + return parseShape (xml, line); + } - String sizeAndStyle (fontDescription.substring (separator + 1)); + Drawable* parsePolygon (const XmlElement& xml, const bool isPolyline) const + { + const String points (xml.getStringAttribute ("points")); + Path path; - float height = sizeAndStyle.getFloatValue(); - if (height <= 0) - height = 10.0f; + int index = 0; + float x, y; - int flags = Font::plain; - if (sizeAndStyle.containsIgnoreCase ("bold")) - flags |= Font::bold; - if (sizeAndStyle.containsIgnoreCase ("italic")) - flags |= Font::italic; + if (parseCoords (points, x, y, index, true)) + { + float firstX = x; + float firstY = y; + float lastX = 0, lastY = 0; - return Font (name, height, flags); -} + path.startNewSubPath (x, y); -class TypefaceCache : public DeletedAtShutdown -{ -public: - TypefaceCache (int numToCache = 10) throw() - : counter (1) - { - while (--numToCache >= 0) - faces.add (new CachedFace()); + while (parseCoords (points, x, y, index, true)) + { + lastX = x; + lastY = y; + path.lineTo (x, y); + } + + if ((! isPolyline) || (firstX == lastX && firstY == lastY)) + path.closeSubPath(); + } + + return parseShape (xml, path); } - ~TypefaceCache() + Drawable* parseShape (const XmlElement& xml, Path& path, + const bool shouldParseTransform = true) const { - clearSingletonInstance(); - } + if (shouldParseTransform && xml.hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); - juce_DeclareSingleton_SingleThreaded_Minimal (TypefaceCache) + return newState.parseShape (xml, path, false); + } - const Typeface::Ptr findTypefaceFor (const Font& font) throw() - { - const int flags = font.getStyleFlags() & (Font::bold | Font::italic); - const String faceName (font.getTypefaceName()); + DrawablePath* dp = new DrawablePath(); + dp->setName (xml.getStringAttribute ("id")); + dp->setFill (Colours::transparentBlack); - int i; - for (i = faces.size(); --i >= 0;) - { - CachedFace* const face = faces.getUnchecked(i); + path.applyTransform (transform); + dp->setPath (path); - if (face->flags == flags - && face->typefaceName == faceName) + Path::Iterator iter (path); + + bool containsClosedSubPath = false; + while (iter.next()) + { + if (iter.elementType == Path::Iterator::closePath) { - face->lastUsageCount = ++counter; - return face->typeFace; + containsClosedSubPath = true; + break; } } - int replaceIndex = 0; - int bestLastUsageCount = std::numeric_limits::max(); + dp->setFill (getPathFillType (path, + getStyleAttribute (&xml, "fill"), + getStyleAttribute (&xml, "fill-opacity"), + getStyleAttribute (&xml, "opacity"), + containsClosedSubPath ? Colours::black + : Colours::transparentBlack)); - for (i = faces.size(); --i >= 0;) + const String strokeType (getStyleAttribute (&xml, "stroke")); + + if (strokeType.isNotEmpty() && ! strokeType.equalsIgnoreCase ("none")) { - const int lu = faces.getUnchecked(i)->lastUsageCount; + dp->setStrokeFill (getPathFillType (path, strokeType, + getStyleAttribute (&xml, "stroke-opacity"), + getStyleAttribute (&xml, "opacity"), + Colours::transparentBlack)); - if (bestLastUsageCount > lu) - { - bestLastUsageCount = lu; - replaceIndex = i; - } + dp->setStrokeType (getStrokeFor (&xml)); } - CachedFace* const face = faces.getUnchecked (replaceIndex); - face->typefaceName = faceName; - face->flags = flags; - face->lastUsageCount = ++counter; - face->typeFace = LookAndFeel::getDefaultLookAndFeel().getTypefaceForFont (font); - jassert (face->typeFace != 0); // the look and feel must return a typeface! - - return face->typeFace; + return dp; } - juce_UseDebuggingNewOperator + const XmlElement* findLinkedElement (const XmlElement* e) const + { + const String id (e->getStringAttribute ("xlink:href")); -private: - struct CachedFace + if (! id.startsWithChar ('#')) + return 0; + + return findElementForId (topLevelXml, id.substring (1)); + } + + void addGradientStopsIn (ColourGradient& cg, const XmlElement* const fillXml) const { - CachedFace() throw() - : lastUsageCount (0), flags (-1) + if (fillXml == 0) + return; + + forEachXmlChildElementWithTagName (*fillXml, e, "stop") { - } + int index = 0; + Colour col (parseColour (getStyleAttribute (e, "stop-color"), index, Colours::black)); - String typefaceName; - int lastUsageCount; - int flags; - Typeface::Ptr typeFace; - }; + const String opacity (getStyleAttribute (e, "stop-opacity", "1")); + col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue())); - int counter; - OwnedArray faces; + double offset = e->getDoubleAttribute ("offset"); - TypefaceCache (const TypefaceCache&); - TypefaceCache& operator= (const TypefaceCache&); -}; + if (e->getStringAttribute ("offset").containsChar ('%')) + offset *= 0.01; -juce_ImplementSingleton_SingleThreaded (TypefaceCache) + cg.addColour (jlimit (0.0, 1.0, offset), col); + } + } -Typeface* Font::getTypeface() const throw() -{ - if (font->typeface == 0) - font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this); + const FillType getPathFillType (const Path& path, + const String& fill, + const String& fillOpacity, + const String& overallOpacity, + const Colour& defaultColour) const + { + float opacity = 1.0f; - return font->typeface; -} + if (overallOpacity.isNotEmpty()) + opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue()); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Font.cpp ***/ + if (fillOpacity.isNotEmpty()) + opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); + if (fill.startsWithIgnoreCase ("url")) + { + const String id (fill.fromFirstOccurrenceOf ("#", false, false) + .upToLastOccurrenceOf (")", false, false).trim()); -/*** Start of inlined file: juce_GlyphArrangement.cpp ***/ -BEGIN_JUCE_NAMESPACE + const XmlElement* const fillXml = findElementForId (topLevelXml, id); -PositionedGlyph::PositionedGlyph (const float x_, const float y_, const float w_, const Font& font_, - const juce_wchar character_, const int glyph_) - : x (x_), - y (y_), - w (w_), - font (font_), - character (character_), - glyph (glyph_) -{ -} + if (fillXml != 0 + && (fillXml->hasTagName ("linearGradient") + || fillXml->hasTagName ("radialGradient"))) + { + const XmlElement* inheritedFrom = findLinkedElement (fillXml); -PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) - : x (other.x), - y (other.y), - w (other.w), - font (other.font), - character (other.character), - glyph (other.glyph) -{ -} + ColourGradient gradient; -void PositionedGlyph::draw (const Graphics& g) const -{ - if (! isWhitespace()) - { - g.getInternalContext()->setFont (font); - g.getInternalContext()->drawGlyph (glyph, AffineTransform::translation (x, y)); - } -} + addGradientStopsIn (gradient, inheritedFrom); + addGradientStopsIn (gradient, fillXml); -void PositionedGlyph::draw (const Graphics& g, - const AffineTransform& transform) const -{ - if (! isWhitespace()) - { - g.getInternalContext()->setFont (font); - g.getInternalContext()->drawGlyph (glyph, AffineTransform::translation (x, y) - .followedBy (transform)); - } -} + if (gradient.getNumColours() > 0) + { + gradient.addColour (0.0, gradient.getColour (0)); + gradient.addColour (1.0, gradient.getColour (gradient.getNumColours() - 1)); + } + else + { + gradient.addColour (0.0, Colours::black); + gradient.addColour (1.0, Colours::black); + } -void PositionedGlyph::createPath (Path& path) const -{ - if (! isWhitespace()) - { - Typeface* const t = font.getTypeface(); + if (overallOpacity.isNotEmpty()) + gradient.multiplyOpacity (overallOpacity.getFloatValue()); - if (t != 0) - { - Path p; - t->getOutlineForGlyph (glyph, p); + jassert (gradient.getNumColours() > 0); - path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) - .translated (x, y)); - } - } -} + gradient.isRadial = fillXml->hasTagName ("radialGradient"); -bool PositionedGlyph::hitTest (float px, float py) const -{ - if (getBounds().contains (px, py) && ! isWhitespace()) - { - Typeface* const t = font.getTypeface(); + float gradientWidth = viewBoxW; + float gradientHeight = viewBoxH; + float dx = 0.0f; + float dy = 0.0f; - if (t != 0) - { - Path p; - t->getOutlineForGlyph (glyph, p); + const bool userSpace = fillXml->getStringAttribute ("gradientUnits").equalsIgnoreCase ("userSpaceOnUse"); - AffineTransform::translation (-x, -y) - .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight()) - .transformPoint (px, py); + if (! userSpace) + { + const Rectangle bounds (path.getBounds()); + dx = bounds.getX(); + dy = bounds.getY(); + gradientWidth = bounds.getWidth(); + gradientHeight = bounds.getHeight(); + } - return p.contains (px, py); - } - } + if (gradient.isRadial) + { + gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("cx", "50%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("cy", "50%"), gradientHeight)); - return false; -} + const float radius = getCoordLength (fillXml->getStringAttribute ("r", "50%"), gradientWidth); + gradient.point2 = gradient.point1 + Point (radius, 0.0f); -void PositionedGlyph::moveBy (const float deltaX, - const float deltaY) -{ - x += deltaX; - y += deltaY; -} + //xxx (the fx, fy focal point isn't handled properly here..) + } + else + { + gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x1", "0%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("y1", "0%"), gradientHeight)); -GlyphArrangement::GlyphArrangement() -{ - glyphs.ensureStorageAllocated (128); -} + gradient.point2.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x2", "100%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("y2", "0%"), gradientHeight)); -GlyphArrangement::GlyphArrangement (const GlyphArrangement& other) -{ - addGlyphArrangement (other); -} + if (gradient.point1 == gradient.point2) + return Colour (gradient.getColour (gradient.getNumColours() - 1)); + } -GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other) -{ - if (this != &other) - { - clear(); - addGlyphArrangement (other); - } + FillType type (gradient); + type.transform = parseTransform (fillXml->getStringAttribute ("gradientTransform")) + .followedBy (transform); + return type; + } + } - return *this; -} + if (fill.equalsIgnoreCase ("none")) + return Colours::transparentBlack; -GlyphArrangement::~GlyphArrangement() -{ -} + int i = 0; + const Colour colour (parseColour (fill, i, defaultColour)); + return colour.withMultipliedAlpha (opacity); + } -void GlyphArrangement::clear() -{ - glyphs.clear(); -} + const PathStrokeType getStrokeFor (const XmlElement* const xml) const + { + const String strokeWidth (getStyleAttribute (xml, "stroke-width")); + const String cap (getStyleAttribute (xml, "stroke-linecap")); + const String join (getStyleAttribute (xml, "stroke-linejoin")); -PositionedGlyph& GlyphArrangement::getGlyph (const int index) const -{ - jassert (((unsigned int) index) < (unsigned int) glyphs.size()); + //const String mitreLimit (getStyleAttribute (xml, "stroke-miterlimit")); + //const String dashArray (getStyleAttribute (xml, "stroke-dasharray")); + //const String dashOffset (getStyleAttribute (xml, "stroke-dashoffset")); - return *glyphs [index]; -} + PathStrokeType::JointStyle joinStyle = PathStrokeType::mitered; + PathStrokeType::EndCapStyle capStyle = PathStrokeType::butt; -void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other) -{ - glyphs.ensureStorageAllocated (glyphs.size() + other.glyphs.size()); - glyphs.addCopiesOf (other.glyphs); -} + if (join.equalsIgnoreCase ("round")) + joinStyle = PathStrokeType::curved; + else if (join.equalsIgnoreCase ("bevel")) + joinStyle = PathStrokeType::beveled; -void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num) -{ - glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num); -} + if (cap.equalsIgnoreCase ("round")) + capStyle = PathStrokeType::rounded; + else if (cap.equalsIgnoreCase ("square")) + capStyle = PathStrokeType::square; -void GlyphArrangement::addLineOfText (const Font& font, - const String& text, - const float xOffset, - const float yOffset) -{ - addCurtailedLineOfText (font, text, - xOffset, yOffset, - 1.0e10f, false); -} + float ox = 0.0f, oy = 0.0f; + float x = getCoordLength (strokeWidth, viewBoxW), y = 0.0f; + transform.transformPoints (ox, oy, x, y); -void GlyphArrangement::addCurtailedLineOfText (const Font& font, - const String& text, - float xOffset, - const float yOffset, - const float maxWidthPixels, - const bool useEllipsis) -{ - if (text.isNotEmpty()) - { - Array newGlyphs; - Array xOffsets; - font.getGlyphPositions (text, newGlyphs, xOffsets); - const int textLen = newGlyphs.size(); + return PathStrokeType (strokeWidth.isNotEmpty() ? juce_hypotf (x - ox, y - oy) : 1.0f, + joinStyle, capStyle); + } - const juce_wchar* const unicodeText = text; + Drawable* parseText (const XmlElement& xml) + { + Array xCoords, yCoords, dxCoords, dyCoords; - for (int i = 0; i < textLen; ++i) - { - const float thisX = xOffsets.getUnchecked (i); - const float nextX = xOffsets.getUnchecked (i + 1); + getCoordList (xCoords, getInheritedAttribute (&xml, "x"), true, true); + getCoordList (yCoords, getInheritedAttribute (&xml, "y"), true, false); + getCoordList (dxCoords, getInheritedAttribute (&xml, "dx"), true, true); + getCoordList (dyCoords, getInheritedAttribute (&xml, "dy"), true, false); - if (nextX > maxWidthPixels + 1.0f) + //xxx not done text yet! + + forEachXmlChildElement (xml, e) + { + if (e->isTextElement()) { - // curtail the string if it's too wide.. - if (useEllipsis && textLen > 3 && glyphs.size() >= 3) - insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size()); + const String text (e->getText()); - break; + Path path; + Drawable* s = parseShape (*e, path); + delete s; } - else + else if (e->hasTagName ("tspan")) { - glyphs.add (new PositionedGlyph (xOffset + thisX, yOffset, nextX - thisX, - font, unicodeText[i], newGlyphs.getUnchecked(i))); + Drawable* s = parseText (*e); + delete s; } } - } -} -int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos, - const int startIndex, int endIndex) -{ - int numDeleted = 0; + return 0; + } - if (glyphs.size() > 0) + void addTransform (const XmlElement& xml) { - Array dotGlyphs; - Array dotXs; - font.getGlyphPositions ("..", dotGlyphs, dotXs); + transform = parseTransform (xml.getStringAttribute ("transform")) + .followedBy (transform); + } - const float dx = dotXs[1]; - float xOffset = 0.0f, yOffset = 0.0f; + bool parseCoord (const String& s, float& value, int& index, + const bool allowUnits, const bool isX) const + { + String number; - while (endIndex > startIndex) + if (! parseNextNumber (s, number, index, allowUnits)) { - const PositionedGlyph* pg = glyphs.getUnchecked (--endIndex); - xOffset = pg->x; - yOffset = pg->y; + value = 0; + return false; + } - glyphs.remove (endIndex); - ++numDeleted; + value = getCoordLength (number, isX ? viewBoxW : viewBoxH); + return true; + } - if (xOffset + dx * 3 <= maxXPos) - break; - } + bool parseCoords (const String& s, float& x, float& y, + int& index, const bool allowUnits) const + { + return parseCoord (s, x, index, allowUnits, true) + && parseCoord (s, y, index, allowUnits, false); + } - for (int i = 3; --i >= 0;) + float getCoordLength (const String& s, const float sizeForProportions) const + { + float n = s.getFloatValue(); + const int len = s.length(); + + if (len > 2) { - glyphs.insert (endIndex++, new PositionedGlyph (xOffset, yOffset, dx, - font, '.', dotGlyphs.getFirst())); - --numDeleted; - xOffset += dx; + const float dpi = 96.0f; - if (xOffset > maxXPos) - break; + const juce_wchar n1 = s [len - 2]; + const juce_wchar n2 = s [len - 1]; + + if (n1 == 'i' && n2 == 'n') + n *= dpi; + else if (n1 == 'm' && n2 == 'm') + n *= dpi / 25.4f; + else if (n1 == 'c' && n2 == 'm') + n *= dpi / 2.54f; + else if (n1 == 'p' && n2 == 'c') + n *= 15.0f; + else if (n2 == '%') + n *= 0.01f * sizeForProportions; } + + return n; } - return numDeleted; -} + void getCoordList (Array & coords, const String& list, + const bool allowUnits, const bool isX) const + { + int index = 0; + float value; -void GlyphArrangement::addJustifiedText (const Font& font, - const String& text, - float x, float y, - const float maxLineWidth, - const Justification& horizontalLayout) -{ - int lineStartIndex = glyphs.size(); - addLineOfText (font, text, x, y); + while (parseCoord (list, value, index, allowUnits, isX)) + coords.add (value); + } - const float originalY = y; + void parseCSSStyle (const XmlElement& xml) + { + cssStyleText = xml.getAllSubText() + "\n" + cssStyleText; + } - while (lineStartIndex < glyphs.size()) + const String getStyleAttribute (const XmlElement* xml, const String& attributeName, + const String& defaultValue = String::empty) const { - int i = lineStartIndex; + if (xml->hasAttribute (attributeName)) + return xml->getStringAttribute (attributeName, defaultValue); - if (glyphs.getUnchecked(i)->getCharacter() != '\n' - && glyphs.getUnchecked(i)->getCharacter() != '\r') - ++i; + const String styleAtt (xml->getStringAttribute ("style")); - const float lineMaxX = glyphs.getUnchecked (lineStartIndex)->getLeft() + maxLineWidth; - int lastWordBreakIndex = -1; + if (styleAtt.isNotEmpty()) + { + const String value (getAttributeFromStyleList (styleAtt, attributeName, String::empty)); - while (i < glyphs.size()) + if (value.isNotEmpty()) + return value; + } + else if (xml->hasAttribute ("class")) { - const PositionedGlyph* pg = glyphs.getUnchecked (i); - const juce_wchar c = pg->getCharacter(); + const String className ("." + xml->getStringAttribute ("class")); - if (c == '\r' || c == '\n') - { - ++i; + int index = cssStyleText.indexOfIgnoreCase (className + " "); - if (c == '\r' && i < glyphs.size() - && glyphs.getUnchecked(i)->getCharacter() == '\n') - ++i; + if (index < 0) + index = cssStyleText.indexOfIgnoreCase (className + "{"); - break; - } - else if (pg->isWhitespace()) - { - lastWordBreakIndex = i + 1; - } - else if (pg->getRight() - 0.0001f >= lineMaxX) + if (index >= 0) { - if (lastWordBreakIndex >= 0) - i = lastWordBreakIndex; - - break; - } + const int openBracket = cssStyleText.indexOfChar (index, '{'); - ++i; - } + if (openBracket > index) + { + const int closeBracket = cssStyleText.indexOfChar (openBracket, '}'); - const float currentLineStartX = glyphs.getUnchecked (lineStartIndex)->getLeft(); - float currentLineEndX = currentLineStartX; + if (closeBracket > openBracket) + { + const String value (getAttributeFromStyleList (cssStyleText.substring (openBracket + 1, closeBracket), attributeName, defaultValue)); - for (int j = i; --j >= lineStartIndex;) - { - if (! glyphs.getUnchecked (j)->isWhitespace()) - { - currentLineEndX = glyphs.getUnchecked (j)->getRight(); - break; + if (value.isNotEmpty()) + return value; + } + } } } - float deltaX = 0.0f; + xml = const_cast (topLevelXml)->findParentElementOf (xml); - 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); + if (xml != 0) + return getStyleAttribute (xml, attributeName, defaultValue); - moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, - x + deltaX - currentLineStartX, y - originalY); + return defaultValue; + } - lineStartIndex = i; + const String getInheritedAttribute (const XmlElement* xml, const String& attributeName) const + { + if (xml->hasAttribute (attributeName)) + return xml->getStringAttribute (attributeName); - y += font.getHeight(); + xml = const_cast (topLevelXml)->findParentElementOf (xml); + + if (xml != 0) + return getInheritedAttribute (xml, attributeName); + + return String::empty; } -} -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); + static bool isIdentifierChar (const juce_wchar c) + { + return CharacterFunctions::isLetter (c) || c == '-'; + } - if (text.containsAnyOf ("\r\n")) + static const String getAttributeFromStyleList (const String& list, const String& attributeName, const String& defaultValue) { - GlyphArrangement ga; - ga.addJustifiedText (f, text, x, y, width, layout); + int i = 0; - const Rectangle bb (ga.getBoundingBox (0, -1, false)); + for (;;) + { + i = list.indexOf (i, attributeName); - float dy = y - bb.getY(); + if (i < 0) + break; - if (layout.testFlags (Justification::verticallyCentred)) - dy += (height - bb.getHeight()) * 0.5f; - else if (layout.testFlags (Justification::bottom)) - dy += height - bb.getHeight(); + if ((i == 0 || (i > 0 && ! isIdentifierChar (list [i - 1]))) + && ! isIdentifierChar (list [i + attributeName.length()])) + { + i = list.indexOfChar (i, ':'); - ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); + if (i < 0) + break; - glyphs.ensureStorageAllocated (glyphs.size() + ga.glyphs.size()); + int end = list.indexOfChar (i, ';'); - for (int i = 0; i < ga.glyphs.size(); ++i) - glyphs.add (ga.glyphs.getUnchecked (i)); + if (end < 0) + end = 0x7ffff; - ga.glyphs.clear (false); - return; - } + return list.substring (i + 1, end).trim(); + } - int startIndex = glyphs.size(); - addLineOfText (f, text.trim(), x, y); + ++i; + } - if (glyphs.size() > startIndex) + return defaultValue; + } + + static bool parseNextNumber (const String& source, String& value, int& index, const bool allowUnits) { - float lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - - glyphs.getUnchecked (startIndex)->getLeft(); + const juce_wchar* const s = source; - if (lineWidth <= 0) - return; + while (CharacterFunctions::isWhitespace (s[index]) || s[index] == ',') + ++index; - if (lineWidth * minimumHorizontalScale < width) + int start = index; + + if (CharacterFunctions::isDigit (s[index]) || s[index] == '.' || s[index] == '-') + ++index; + + while (CharacterFunctions::isDigit (s[index]) || s[index] == '.') + ++index; + + if ((s[index] == 'e' || s[index] == 'E') + && (CharacterFunctions::isDigit (s[index + 1]) + || s[index + 1] == '-' + || s[index + 1] == '+')) { - if (lineWidth > width) - stretchRangeOfGlyphs (startIndex, glyphs.size() - startIndex, - width / lineWidth); + index += 2; - justifyGlyphs (startIndex, glyphs.size() - startIndex, - x, y, width, height, layout); + while (CharacterFunctions::isDigit (s[index])) + ++index; } - else if (maximumLines <= 1) + + if (allowUnits) { - fitLineIntoSpace (startIndex, glyphs.size() - startIndex, - x, y, width, height, f, layout, minimumHorizontalScale); + while (CharacterFunctions::isLetter (s[index])) + ++index; } - 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; + if (index == start) + return false; - maximumLines = jmin (maximumLines, length); + value = String (s + start, index - start); - while (numLines < maximumLines) + while (CharacterFunctions::isWhitespace (s[index]) || s[index] == ',') + ++index; + + return true; + } + + static const Colour parseColour (const String& s, int& index, const Colour& defaultColour) + { + if (s [index] == '#') + { + uint32 hex [6]; + zeromem (hex, sizeof (hex)); + int numChars = 0; + + for (int i = 6; --i >= 0;) { - ++numLines; + const int hexValue = CharacterFunctions::getHexDigitValue (s [++index]); - const float newFontHeight = height / (float) numLines; + if (hexValue >= 0) + hex [numChars++] = hexValue; + else + break; + } - if (newFontHeight < font.getHeight()) - { - font.setHeight (jmax (8.0f, newFontHeight)); + if (numChars <= 3) + return Colour ((uint8) (hex [0] * 0x11), + (uint8) (hex [1] * 0x11), + (uint8) (hex [2] * 0x11)); + else + return Colour ((uint8) ((hex [0] << 4) + hex [1]), + (uint8) ((hex [2] << 4) + hex [3]), + (uint8) ((hex [4] << 4) + hex [5])); + } + else if (s [index] == 'r' + && s [index + 1] == 'g' + && s [index + 2] == 'b') + { + const int openBracket = s.indexOfChar (index, '('); + const int closeBracket = s.indexOfChar (openBracket, ')'); - removeRangeOfGlyphs (startIndex, -1); - addLineOfText (font, txt, x, y); + if (openBracket >= 3 && closeBracket > openBracket) + { + index = closeBracket; - lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - - glyphs.getUnchecked (startIndex)->getLeft(); - } + StringArray tokens; + tokens.addTokens (s.substring (openBracket + 1, closeBracket), ",", ""); + tokens.trim(); + tokens.removeEmptyStrings(); - if (numLines > lineWidth / width || newFontHeight < 8.0f) - break; + if (tokens[0].containsChar ('%')) + return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()), + (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()), + (uint8) roundToInt (2.55 * tokens[2].getDoubleValue())); + else + return Colour ((uint8) tokens[0].getIntValue(), + (uint8) tokens[1].getIntValue(), + (uint8) tokens[2].getIntValue()); } + } - if (numLines < 1) - numLines = 1; + return Colours::findColourForName (s, defaultColour); + } - float lineY = y; - float widthPerLine = lineWidth / numLines; - int lastLineStartIndex = 0; + static const AffineTransform parseTransform (String t) + { + AffineTransform result; - for (int line = 0; line < numLines; ++line) - { - int i = startIndex; - lastLineStartIndex = i; - float lineStartX = glyphs.getUnchecked (startIndex)->getLeft(); + while (t.isNotEmpty()) + { + StringArray tokens; + tokens.addTokens (t.fromFirstOccurrenceOf ("(", false, false) + .upToFirstOccurrenceOf (")", false, false), + ", ", String::empty); - if (line == numLines - 1) - { - widthPerLine = width; - i = glyphs.size(); - } + tokens.removeEmptyStrings (true); + + float numbers [6]; + + for (int i = 0; i < 6; ++i) + numbers[i] = tokens[i].getFloatValue(); + + AffineTransform trans; + + if (t.startsWithIgnoreCase ("matrix")) + { + trans = AffineTransform (numbers[0], numbers[2], numbers[4], + numbers[1], numbers[3], numbers[5]); + } + else if (t.startsWithIgnoreCase ("translate")) + { + jassert (tokens.size() == 2); + trans = AffineTransform::translation (numbers[0], numbers[1]); + } + else if (t.startsWithIgnoreCase ("scale")) + { + if (tokens.size() == 1) + trans = AffineTransform::scale (numbers[0], numbers[0]); else - { - while (i < glyphs.size()) - { - lineWidth = (glyphs.getUnchecked (i)->getRight() - lineStartX); + trans = AffineTransform::scale (numbers[0], numbers[1]); + } + else if (t.startsWithIgnoreCase ("rotate")) + { + if (tokens.size() != 3) + trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi)); + else + trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi), + numbers[1], numbers[2]); + } + else if (t.startsWithIgnoreCase ("skewX")) + { + trans = AffineTransform (1.0f, std::tan (numbers[0] * (float_Pi / 180.0f)), 0.0f, + 0.0f, 1.0f, 0.0f); + } + else if (t.startsWithIgnoreCase ("skewY")) + { + trans = AffineTransform (1.0f, 0.0f, 0.0f, + std::tan (numbers[0] * (float_Pi / 180.0f)), 1.0f, 0.0f); + } - 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; + result = trans.followedBy (result); + t = t.fromFirstOccurrenceOf (")", false, false).trimStart(); + } + + return result; + } + + static void endpointToCentreParameters (const double x1, const double y1, + const double x2, const double y2, + const double angle, + const bool largeArc, const bool sweep, + double& rx, double& ry, + double& centreX, double& centreY, + double& startAngle, double& deltaAngle) + { + const double midX = (x1 - x2) * 0.5; + const double midY = (y1 - y2) * 0.5; + + const double cosAngle = cos (angle); + const double sinAngle = sin (angle); + const double xp = cosAngle * midX + sinAngle * midY; + const double yp = cosAngle * midY - sinAngle * midX; + const double xp2 = xp * xp; + const double yp2 = yp * yp; + + double rx2 = rx * rx; + double ry2 = ry * ry; + + const double s = (xp2 / rx2) + (yp2 / ry2); + double c; + + if (s <= 1.0) + { + c = std::sqrt (jmax (0.0, ((rx2 * ry2) - (rx2 * yp2) - (ry2 * xp2)) + / (( rx2 * yp2) + (ry2 * xp2)))); + + if (largeArc == sweep) + c = -c; + } + else + { + const double s2 = std::sqrt (s); + rx *= s2; + ry *= s2; + rx2 = rx * rx; + ry2 = ry * ry; + c = 0; + } - 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; + const double cpx = ((rx * yp) / ry) * c; + const double cpy = ((-ry * xp) / rx) * c; - 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; - } - } + centreX = ((x1 + x2) * 0.5) + (cosAngle * cpx) - (sinAngle * cpy); + centreY = ((y1 + y2) * 0.5) + (sinAngle * cpx) + (cosAngle * cpy); - break; - } + const double ux = (xp - cpx) / rx; + const double uy = (yp - cpy) / ry; + const double vx = (-xp - cpx) / rx; + const double vy = (-yp - cpy) / ry; - ++i; - } + const double length = juce_hypot (ux, uy); - break; - } + startAngle = acos (jlimit (-1.0, 1.0, ux / length)); - ++i; - } + if (uy < 0) + startAngle = -startAngle; - int wsStart = i; - while (wsStart > 0 && glyphs.getUnchecked (wsStart - 1)->isWhitespace()) - --wsStart; + startAngle += double_Pi * 0.5; - int wsEnd = i; + deltaAngle = acos (jlimit (-1.0, 1.0, ((ux * vx) + (uy * vy)) + / (length * juce_hypot (vx, vy)))); - while (wsEnd < glyphs.size() && glyphs.getUnchecked (wsEnd)->isWhitespace()) - ++wsEnd; + if ((ux * vy) - (uy * vx) < 0) + deltaAngle = -deltaAngle; - removeRangeOfGlyphs (wsStart, wsEnd - wsStart); - i = jmax (wsStart, startIndex + 1); - } + if (sweep) + { + if (deltaAngle < 0) + deltaAngle += double_Pi * 2.0; + } + else + { + if (deltaAngle > 0) + deltaAngle -= double_Pi * 2.0; + } - i -= fitLineIntoSpace (startIndex, i - startIndex, - x, lineY, width, font.getHeight(), font, - layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, - minimumHorizontalScale); + deltaAngle = fmod (deltaAngle, double_Pi * 2.0); + } - startIndex = i; - lineY += font.getHeight(); + static const XmlElement* findElementForId (const XmlElement* const parent, const String& id) + { + forEachXmlChildElement (*parent, e) + { + if (e->compareAttribute ("id", id)) + return e; - if (startIndex >= glyphs.size()) - break; - } + const XmlElement* const found = findElementForId (e, id); - justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, - x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); + if (found != 0) + return found; } + + return 0; } + + SVGState& operator= (const SVGState&); +}; + +Drawable* Drawable::createFromSVG (const XmlElement& svgDocument) +{ + SVGState state (&svgDocument); + return state.parseSVGElement (svgDocument); } -void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, - const float dx, const float dy) +END_JUCE_NAMESPACE +/*** End of inlined file: juce_SVGParser.cpp ***/ + + +/*** Start of inlined file: juce_DropShadowEffect.cpp ***/ +BEGIN_JUCE_NAMESPACE + +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("t", on) +#endif + +DropShadowEffect::DropShadowEffect() + : offsetX (0), + offsetY (0), + radius (4), + opacity (0.6f) { - jassert (startIndex >= 0); +} - if (dx != 0.0f || dy != 0.0f) - { - if (num < 0 || startIndex + num > glyphs.size()) - num = glyphs.size() - startIndex; +DropShadowEffect::~DropShadowEffect() +{ +} - while (--num >= 0) - glyphs.getUnchecked (startIndex++)->moveBy (dx, dy); - } +void DropShadowEffect::setShadowProperties (const float newRadius, + const float newOpacity, + const int newShadowOffsetX, + const int newShadowOffsetY) +{ + radius = jmax (1.1f, newRadius); + offsetX = newShadowOffsetX; + offsetY = newShadowOffsetY; + opacity = newOpacity; } -int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, - const Justification& justification, float minimumHorizontalScale) +void DropShadowEffect::applyEffect (Image& image, Graphics& g) { - int numDeleted = 0; - const float lineStartX = glyphs.getUnchecked (start)->getLeft(); - float lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX; + const int w = image.getWidth(); + const int h = image.getHeight(); - if (lineWidth > w) + Image shadowImage (Image::SingleChannel, w, h, false); + + const Image::BitmapData srcData (image, 0, 0, w, h); + const Image::BitmapData destData (shadowImage, 0, 0, w, h, true); + + const int filter = roundToInt (63.0f / radius); + const int radiusMinus1 = roundToInt ((radius - 1.0f) * 63.0f); + + for (int x = w; --x >= 0;) { - if (minimumHorizontalScale < 1.0f) - { - stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth)); - lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX - 0.5f; - } + int shadowAlpha = 0; - if (lineWidth > w) + const PixelARGB* src = ((const PixelARGB*) srcData.data) + x; + uint8* shadowPix = destData.data + x; + + for (int y = h; --y >= 0;) { - numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs); - numGlyphs -= numDeleted; + shadowAlpha = ((shadowAlpha * radiusMinus1 + (src->getAlpha() << 6)) * filter) >> 12; + + *shadowPix = (uint8) shadowAlpha; + src = (const PixelARGB*) (((const uint8*) src) + srcData.lineStride); + shadowPix += destData.lineStride; } } - justifyGlyphs (start, numGlyphs, x, y, w, h, justification); - return numDeleted; -} - -void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, - const float horizontalScaleFactor) -{ - jassert (startIndex >= 0); - - if (num < 0 || startIndex + num > glyphs.size()) - num = glyphs.size() - startIndex; - - if (num > 0) + for (int y = h; --y >= 0;) { - const float xAnchor = glyphs.getUnchecked (startIndex)->getLeft(); + int shadowAlpha = 0; + uint8* shadowPix = destData.getLinePointer (y); - while (--num >= 0) + for (int x = w; --x >= 0;) { - PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++); - - pg->x = xAnchor + (pg->x - xAnchor) * horizontalScaleFactor; - pg->font.setHorizontalScale (pg->font.getHorizontalScale() * horizontalScaleFactor); - pg->w *= horizontalScaleFactor; + shadowAlpha = ((shadowAlpha * radiusMinus1 + (*shadowPix << 6)) * filter) >> 12; + *shadowPix++ = (uint8) shadowAlpha; } } + + g.setColour (Colours::black.withAlpha (opacity)); + g.drawImageAt (shadowImage, offsetX, offsetY, true); + + g.setOpacity (1.0f); + g.drawImageAt (image, 0, 0); } -const Rectangle GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const -{ - jassert (startIndex >= 0); +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("", on) // resets optimisations to the project defaults +#endif - if (num < 0 || startIndex + num > glyphs.size()) - num = glyphs.size() - startIndex; +END_JUCE_NAMESPACE +/*** End of inlined file: juce_DropShadowEffect.cpp ***/ - Rectangle result; - while (--num >= 0) - { - const PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++); +/*** Start of inlined file: juce_GlowEffect.cpp ***/ +BEGIN_JUCE_NAMESPACE - if (includeWhitespace || ! pg->isWhitespace()) - result = result.getUnion (pg->getBounds()); - } +GlowEffect::GlowEffect() + : radius (2.0f), + colour (Colours::white) +{ +} - return result; +GlowEffect::~GlowEffect() +{ } -void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, - const float x, const float y, const float width, const float height, - const Justification& justification) +void GlowEffect::setGlowProperties (const float newRadius, + const Colour& newColour) { - jassert (num >= 0 && startIndex >= 0); + radius = newRadius; + colour = newColour; +} - if (glyphs.size() > 0 && num > 0) - { - const Rectangle bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified - | Justification::horizontallyCentred))); - float deltaX = 0.0f; +void GlowEffect::applyEffect (Image& image, Graphics& g) +{ + Image temp (image.getFormat(), image.getWidth(), image.getHeight(), true); - 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(); + ImageConvolutionKernel blurKernel (roundToInt (radius * 2.0f)); - float deltaY = 0.0f; + blurKernel.createGaussianBlur (radius); + blurKernel.rescaleAllValues (radius); - 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(); + blurKernel.applyToImage (temp, image, image.getBounds()); - moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); + g.setColour (colour); + g.drawImageAt (temp, 0, 0, true); - if (justification.testFlags (Justification::horizontallyJustified)) - { - int lineStart = 0; - float baseY = glyphs.getUnchecked (startIndex)->getBaselineY(); + g.setOpacity (1.0f); + g.drawImageAt (image, 0, 0, false); +} - int i; - for (i = 0; i < num; ++i) - { - const float glyphY = glyphs.getUnchecked (startIndex + i)->getBaselineY(); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_GlowEffect.cpp ***/ - if (glyphY != baseY) - { - spreadOutLine (startIndex + lineStart, i - lineStart, width); - lineStart = i; - baseY = glyphY; - } - } +/*** Start of inlined file: juce_ReduceOpacityEffect.cpp ***/ +BEGIN_JUCE_NAMESPACE - if (i > lineStart) - spreadOutLine (startIndex + lineStart, i - lineStart, width); - } - } +ReduceOpacityEffect::ReduceOpacityEffect (const float opacity_) + : opacity (opacity_) +{ } -void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth) +ReduceOpacityEffect::~ReduceOpacityEffect() { - if (start + num < glyphs.size() - && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\r' - && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\n') - { - int numSpaces = 0; - int spacesAtEnd = 0; - - for (int i = 0; i < num; ++i) - { - if (glyphs.getUnchecked (start + i)->isWhitespace()) - { - ++spacesAtEnd; - ++numSpaces; - } - else - { - spacesAtEnd = 0; - } - } +} - numSpaces -= spacesAtEnd; +void ReduceOpacityEffect::setOpacity (const float newOpacity) +{ + opacity = jlimit (0.0f, 1.0f, newOpacity); +} - if (numSpaces > 0) - { - const float startX = glyphs.getUnchecked (start)->getLeft(); - const float endX = glyphs.getUnchecked (start + num - 1 - spacesAtEnd)->getRight(); +void ReduceOpacityEffect::applyEffect (Image& image, Graphics& g) +{ + g.setOpacity (opacity); + g.drawImageAt (image, 0, 0); +} - const float extraPaddingBetweenWords - = (targetWidth - (endX - startX)) / (float) numSpaces; +END_JUCE_NAMESPACE +/*** End of inlined file: juce_ReduceOpacityEffect.cpp ***/ - float deltaX = 0.0f; - for (int i = 0; i < num; ++i) - { - glyphs.getUnchecked (start + i)->moveBy (deltaX, 0.0f); +/*** Start of inlined file: juce_Font.cpp ***/ +BEGIN_JUCE_NAMESPACE - if (glyphs.getUnchecked (start + i)->isWhitespace()) - deltaX += extraPaddingBetweenWords; - } - } +namespace FontValues +{ + static float limitFontHeight (const float height) throw() + { + return jlimit (0.1f, 10000.0f, height); } + + static const float defaultFontHeight = 14.0f; + + static String fallbackFont; } -void GlyphArrangement::draw (const Graphics& g) const +Font::SharedFontInternal::SharedFontInternal (const String& typefaceName_, const float height_, const float horizontalScale_, + const float kerning_, const float ascent_, const int styleFlags_, + Typeface* const typeface_) throw() + : typefaceName (typefaceName_), + height (height_), + horizontalScale (horizontalScale_), + kerning (kerning_), + ascent (ascent_), + styleFlags (styleFlags_), + typeface (typeface_) { - for (int i = 0; i < glyphs.size(); ++i) - { - const PositionedGlyph* const pg = glyphs.getUnchecked(i); - - if (pg->font.isUnderlined()) - { - const float lineThickness = (pg->font.getDescent()) * 0.3f; +} - float nextX = pg->x + pg->w; +Font::SharedFontInternal::SharedFontInternal (const SharedFontInternal& other) throw() + : typefaceName (other.typefaceName), + height (other.height), + horizontalScale (other.horizontalScale), + kerning (other.kerning), + ascent (other.ascent), + styleFlags (other.styleFlags), + typeface (other.typeface) +{ +} - if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y) - nextX = glyphs.getUnchecked (i + 1)->x; +Font::Font() throw() + : font (new SharedFontInternal (getDefaultSansSerifFontName(), FontValues::defaultFontHeight, + 1.0f, 0, 0, Font::plain, 0)) +{ +} - g.fillRect (pg->x, pg->y + lineThickness * 2.0f, - nextX - pg->x, lineThickness); - } +Font::Font (const float fontHeight, const int styleFlags_) throw() + : font (new SharedFontInternal (getDefaultSansSerifFontName(), FontValues::limitFontHeight (fontHeight), + 1.0f, 0, 0, styleFlags_, 0)) +{ +} - pg->draw (g); - } +Font::Font (const String& typefaceName_, + const float fontHeight, + const int styleFlags_) throw() + : font (new SharedFontInternal (typefaceName_, FontValues::limitFontHeight (fontHeight), + 1.0f, 0, 0, styleFlags_, 0)) +{ } -void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const +Font::Font (const Font& other) throw() + : font (other.font) { - for (int i = 0; i < glyphs.size(); ++i) - { - const PositionedGlyph* const pg = glyphs.getUnchecked(i); +} - if (pg->font.isUnderlined()) - { - const float lineThickness = (pg->font.getDescent()) * 0.3f; +Font& Font::operator= (const Font& other) throw() +{ + font = other.font; + return *this; +} - float nextX = pg->x + pg->w; +Font::~Font() throw() +{ +} - if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y) - nextX = glyphs.getUnchecked (i + 1)->x; +Font::Font (const Typeface::Ptr& typeface) throw() + : font (new SharedFontInternal (typeface->getName(), FontValues::defaultFontHeight, + 1.0f, 0, 0, Font::plain, typeface)) +{ +} - Path p; - p.addLineSegment (Line (pg->x, pg->y + lineThickness * 2.0f, - nextX, pg->y + lineThickness * 2.0f), - lineThickness); +bool Font::operator== (const Font& other) const throw() +{ + return font == other.font + || (font->height == other.font->height + && font->styleFlags == other.font->styleFlags + && font->horizontalScale == other.font->horizontalScale + && font->kerning == other.font->kerning + && font->typefaceName == other.font->typefaceName); +} - g.fillPath (p, transform); - } +bool Font::operator!= (const Font& other) const throw() +{ + return ! operator== (other); +} - pg->draw (g, transform); - } +void Font::dupeInternalIfShared() throw() +{ + if (font->getReferenceCount() > 1) + font = new SharedFontInternal (*font); } -void GlyphArrangement::createPath (Path& path) const +const String Font::getDefaultSansSerifFontName() throw() { - for (int i = 0; i < glyphs.size(); ++i) - glyphs.getUnchecked (i)->createPath (path); + static const String name (""); + return name; } -int GlyphArrangement::findGlyphIndexAt (float x, float y) const +const String Font::getDefaultSerifFontName() throw() { - for (int i = 0; i < glyphs.size(); ++i) - if (glyphs.getUnchecked (i)->hitTest (x, y)) - return i; + static const String name (""); + return name; +} - return -1; +const String Font::getDefaultMonospacedFontName() throw() +{ + static const String name (""); + return name; } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_GlyphArrangement.cpp ***/ +void Font::setTypefaceName (const String& faceName) throw() +{ + if (faceName != font->typefaceName) + { + dupeInternalIfShared(); + font->typefaceName = faceName; + font->typeface = 0; + font->ascent = 0; + } +} +const String Font::getFallbackFontName() throw() +{ + return FontValues::fallbackFont; +} -/*** Start of inlined file: juce_TextLayout.cpp ***/ -BEGIN_JUCE_NAMESPACE +void Font::setFallbackFontName (const String& name) throw() +{ + FontValues::fallbackFont = name; +} -class TextLayout::Token +void Font::setHeight (float newHeight) throw() { -public: - String text; - Font font; - int x, y, w, h; - int line, lineHeight; - bool isWhitespace, isNewLine; + newHeight = FontValues::limitFontHeight (newHeight); - Token (const String& t, - const Font& f, - const bool isWhitespace_) - : text (t), - font (f), - x(0), - y(0), - isWhitespace (isWhitespace_) + if (font->height != newHeight) { - w = font.getStringWidth (t); - h = roundToInt (f.getHeight()); - isNewLine = t.containsChar ('\n') || t.containsChar ('\r'); + dupeInternalIfShared(); + font->height = newHeight; } +} - Token (const Token& other) - : text (other.text), - font (other.font), - x (other.x), - y (other.y), - w (other.w), - h (other.h), - line (other.line), - lineHeight (other.lineHeight), - isWhitespace (other.isWhitespace), - isNewLine (other.isNewLine) +void Font::setHeightWithoutChangingWidth (float newHeight) throw() +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight) { + dupeInternalIfShared(); + font->horizontalScale *= (font->height / newHeight); + font->height = newHeight; } +} - ~Token() +void Font::setStyleFlags (const int newFlags) throw() +{ + if (font->styleFlags != newFlags) { + dupeInternalIfShared(); + font->styleFlags = newFlags; + font->typeface = 0; + font->ascent = 0; } +} - void draw (Graphics& g, - const int xOffset, - const int yOffset) +void Font::setSizeAndStyle (float newHeight, + const int newStyleFlags, + const float newHorizontalScale, + const float newKerningAmount) throw() +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight + || font->horizontalScale != newHorizontalScale + || font->kerning != newKerningAmount) { - if (! isWhitespace) - { - g.setFont (font); - g.drawSingleLineText (text.trimEnd(), - xOffset + x, - yOffset + y + (lineHeight - h) - + roundToInt (font.getAscent())); - } + dupeInternalIfShared(); + font->height = newHeight; + font->horizontalScale = newHorizontalScale; + font->kerning = newKerningAmount; } - juce_UseDebuggingNewOperator -}; + setStyleFlags (newStyleFlags); +} -TextLayout::TextLayout() - : totalLines (0) +void Font::setHorizontalScale (const float scaleFactor) throw() { - tokens.ensureStorageAllocated (64); + dupeInternalIfShared(); + font->horizontalScale = scaleFactor; } -TextLayout::TextLayout (const String& text, const Font& font) - : totalLines (0) +void Font::setExtraKerningFactor (const float extraKerning) throw() { - tokens.ensureStorageAllocated (64); - appendText (text, font); + dupeInternalIfShared(); + font->kerning = extraKerning; } -TextLayout::TextLayout (const TextLayout& other) - : totalLines (0) +void Font::setBold (const bool shouldBeBold) throw() { - *this = other; + setStyleFlags (shouldBeBold ? (font->styleFlags | bold) + : (font->styleFlags & ~bold)); } -TextLayout& TextLayout::operator= (const TextLayout& other) +bool Font::isBold() const throw() { - if (this != &other) - { - clear(); + return (font->styleFlags & bold) != 0; +} - totalLines = other.totalLines; - tokens.addCopiesOf (other.tokens); - } +void Font::setItalic (const bool shouldBeItalic) throw() +{ + setStyleFlags (shouldBeItalic ? (font->styleFlags | italic) + : (font->styleFlags & ~italic)); +} - return *this; +bool Font::isItalic() const throw() +{ + return (font->styleFlags & italic) != 0; } -TextLayout::~TextLayout() +void Font::setUnderline (const bool shouldBeUnderlined) throw() { - clear(); + setStyleFlags (shouldBeUnderlined ? (font->styleFlags | underlined) + : (font->styleFlags & ~underlined)); } -void TextLayout::clear() +bool Font::isUnderlined() const throw() { - tokens.clear(); - totalLines = 0; + return (font->styleFlags & underlined) != 0; } -void TextLayout::appendText (const String& text, const Font& font) +float Font::getAscent() const throw() { - const juce_wchar* t = text; - String currentString; - int lastCharType = 0; + if (font->ascent == 0) + font->ascent = getTypeface()->getAscent(); - for (;;) - { - const juce_wchar c = *t++; - if (c == 0) - break; + return font->height * font->ascent; +} - int charType; - if (c == '\r' || c == '\n') - { - charType = 0; - } - else if (CharacterFunctions::isWhitespace (c)) - { - charType = 2; - } - else - { - charType = 1; - } +float Font::getDescent() const throw() +{ + return font->height - getAscent(); +} - if (charType == 0 || charType != lastCharType) - { - if (currentString.isNotEmpty()) - { - tokens.add (new Token (currentString, font, - lastCharType == 2 || lastCharType == 0)); - } +int Font::getStringWidth (const String& text) const throw() +{ + return roundToInt (getStringWidthFloat (text)); +} - currentString = String::charToString (c); +float Font::getStringWidthFloat (const String& text) const throw() +{ + float w = getTypeface()->getStringWidth (text); - if (c == '\r' && *t == '\n') - currentString += *t++; + if (font->kerning != 0) + w += font->kerning * text.length(); + + return w * font->height * font->horizontalScale; +} + +void Font::getGlyphPositions (const String& text, Array & glyphs, Array & xOffsets) const throw() +{ + getTypeface()->getGlyphPositions (text, glyphs, xOffsets); + + const float scale = font->height * font->horizontalScale; + const int num = xOffsets.size(); + + if (num > 0) + { + float* const x = &(xOffsets.getReference(0)); + + if (font->kerning != 0) + { + for (int i = 0; i < num; ++i) + x[i] = (x[i] + i * font->kerning) * scale; } else { - currentString += c; + for (int i = 0; i < num; ++i) + x[i] *= scale; } - - lastCharType = charType; } - - if (currentString.isNotEmpty()) - tokens.add (new Token (currentString, font, lastCharType == 2)); } -void TextLayout::setText (const String& text, const Font& font) +void Font::findFonts (Array& destArray) throw() { - clear(); - appendText (text, font); + const StringArray names (findAllTypefaceNames()); + + for (int i = 0; i < names.size(); ++i) + destArray.add (Font (names[i], FontValues::defaultFontHeight, Font::plain)); } -void TextLayout::layout (int maxWidth, - const Justification& justification, - const bool attemptToBalanceLineLengths) +const String Font::toString() const { - if (attemptToBalanceLineLengths) - { - const int originalW = maxWidth; - int bestWidth = maxWidth; - float bestLineProportion = 0.0f; + String s (getTypefaceName()); - while (maxWidth > originalW / 2) - { - layout (maxWidth, justification, false); + if (s == getDefaultSansSerifFontName()) + s = String::empty; + else + s += "; "; - if (getNumLines() <= 1) - return; + s += String (getHeight(), 1); - const int lastLineW = getLineWidth (getNumLines() - 1); - const int lastButOneLineW = getLineWidth (getNumLines() - 2); + if (isBold()) + s += " bold"; - const float prop = lastLineW / (float) lastButOneLineW; + if (isItalic()) + s += " italic"; - if (prop > 0.9f) - return; + return s; +} - if (prop > bestLineProportion) - { - bestLineProportion = prop; - bestWidth = maxWidth; - } +const Font Font::fromString (const String& fontDescription) +{ + String name; - maxWidth -= 10; - } + const int separator = fontDescription.indexOfChar (';'); - layout (bestWidth, justification, false); - } - else - { - int x = 0; - int y = 0; - int h = 0; - totalLines = 0; - int i; + if (separator > 0) + name = fontDescription.substring (0, separator).trim(); - for (i = 0; i < tokens.size(); ++i) - { - Token* const t = tokens.getUnchecked(i); - t->x = x; - t->y = y; - t->line = totalLines; - x += t->w; - h = jmax (h, t->h); + if (name.isEmpty()) + name = getDefaultSansSerifFontName(); - const Token* nextTok = tokens [i + 1]; + String sizeAndStyle (fontDescription.substring (separator + 1)); - if (nextTok == 0) - break; + float height = sizeAndStyle.getFloatValue(); + if (height <= 0) + height = 10.0f; - if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->w > maxWidth)) - { - // finished a line, so go back and update the heights of the things on it - for (int j = i; j >= 0; --j) - { - Token* const tok = tokens.getUnchecked(j); + int flags = Font::plain; + if (sizeAndStyle.containsIgnoreCase ("bold")) + flags |= Font::bold; + if (sizeAndStyle.containsIgnoreCase ("italic")) + flags |= Font::italic; - if (tok->line == totalLines) - tok->lineHeight = h; - else - break; - } + return Font (name, height, flags); +} - x = 0; - y += h; - h = 0; - ++totalLines; - } - } +class TypefaceCache : public DeletedAtShutdown +{ +public: + TypefaceCache (int numToCache = 10) throw() + : counter (1) + { + while (--numToCache >= 0) + faces.add (new CachedFace()); + } - // finished a line, so go back and update the heights of the things on it - for (int j = jmin (i, tokens.size() - 1); j >= 0; --j) - { - Token* const t = tokens.getUnchecked(j); + ~TypefaceCache() + { + clearSingletonInstance(); + } - if (t->line == totalLines) - t->lineHeight = h; - else - break; - } + juce_DeclareSingleton_SingleThreaded_Minimal (TypefaceCache) - ++totalLines; + const Typeface::Ptr findTypefaceFor (const Font& font) throw() + { + const int flags = font.getStyleFlags() & (Font::bold | Font::italic); + const String faceName (font.getTypefaceName()); - if (! justification.testFlags (Justification::left)) + int i; + for (i = faces.size(); --i >= 0;) { - int totalW = getWidth(); + CachedFace* const face = faces.getUnchecked(i); - for (i = totalLines; --i >= 0;) + if (face->flags == flags + && face->typefaceName == faceName) { - const int lineW = getLineWidth (i); + face->lastUsageCount = ++counter; + return face->typeFace; + } + } - int dx = 0; - if (justification.testFlags (Justification::horizontallyCentred)) - dx = (totalW - lineW) / 2; - else if (justification.testFlags (Justification::right)) - dx = totalW - lineW; + int replaceIndex = 0; + int bestLastUsageCount = std::numeric_limits::max(); - for (int j = tokens.size(); --j >= 0;) - { - Token* const t = tokens.getUnchecked(j); + for (i = faces.size(); --i >= 0;) + { + const int lu = faces.getUnchecked(i)->lastUsageCount; - if (t->line == i) - t->x += dx; - } + if (bestLastUsageCount > lu) + { + bestLastUsageCount = lu; + replaceIndex = i; } } - } -} - -int TextLayout::getLineWidth (const int lineNumber) const -{ - int maxW = 0; - for (int i = tokens.size(); --i >= 0;) - { - const Token* const t = tokens.getUnchecked(i); + CachedFace* const face = faces.getUnchecked (replaceIndex); + face->typefaceName = faceName; + face->flags = flags; + face->lastUsageCount = ++counter; + face->typeFace = LookAndFeel::getDefaultLookAndFeel().getTypefaceForFont (font); + jassert (face->typeFace != 0); // the look and feel must return a typeface! - if (t->line == lineNumber && ! t->isWhitespace) - maxW = jmax (maxW, t->x + t->w); + return face->typeFace; } - return maxW; -} - -int TextLayout::getWidth() const -{ - int maxW = 0; + juce_UseDebuggingNewOperator - for (int i = tokens.size(); --i >= 0;) +private: + struct CachedFace { - const Token* const t = tokens.getUnchecked(i); - if (! t->isWhitespace) - maxW = jmax (maxW, t->x + t->w); - } - - return maxW; -} - -int TextLayout::getHeight() const -{ - int maxH = 0; + CachedFace() throw() + : lastUsageCount (0), flags (-1) + { + } - for (int i = tokens.size(); --i >= 0;) - { - const Token* const t = tokens.getUnchecked(i); + String typefaceName; + int lastUsageCount; + int flags; + Typeface::Ptr typeFace; + }; - if (! t->isWhitespace) - maxH = jmax (maxH, t->y + t->h); - } + int counter; + OwnedArray faces; - return maxH; -} + TypefaceCache (const TypefaceCache&); + TypefaceCache& operator= (const TypefaceCache&); +}; -void TextLayout::draw (Graphics& g, - const int xOffset, - const int yOffset) const -{ - for (int i = tokens.size(); --i >= 0;) - tokens.getUnchecked(i)->draw (g, xOffset, yOffset); -} +juce_ImplementSingleton_SingleThreaded (TypefaceCache) -void TextLayout::drawWithin (Graphics& g, - int x, int y, int w, int h, - const Justification& justification) const +Typeface* Font::getTypeface() const throw() { - justification.applyToRectangle (x, y, getWidth(), getHeight(), - x, y, w, h); + if (font->typeface == 0) + font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this); - draw (g, x, y); + return font->typeface; } END_JUCE_NAMESPACE -/*** End of inlined file: juce_TextLayout.cpp ***/ +/*** End of inlined file: juce_Font.cpp ***/ -/*** Start of inlined file: juce_Typeface.cpp ***/ +/*** Start of inlined file: juce_GlyphArrangement.cpp ***/ BEGIN_JUCE_NAMESPACE -Typeface::Typeface (const String& name_) throw() - : name (name_) +PositionedGlyph::PositionedGlyph (const float x_, const float y_, const float w_, const Font& font_, + const juce_wchar character_, const int glyph_) + : x (x_), + y (y_), + w (w_), + font (font_), + character (character_), + glyph (glyph_) { } -Typeface::~Typeface() +PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) + : x (other.x), + y (other.y), + w (other.w), + font (other.font), + character (other.character), + glyph (other.glyph) { } -class CustomTypeface::GlyphInfo +void PositionedGlyph::draw (const Graphics& g) const { -public: - GlyphInfo (const juce_wchar character_, const Path& path_, const float width_) throw() - : character (character_), path (path_), width (width_) + if (! isWhitespace()) { + g.getInternalContext()->setFont (font); + g.getInternalContext()->drawGlyph (glyph, AffineTransform::translation (x, y)); } +} - ~GlyphInfo() throw() +void PositionedGlyph::draw (const Graphics& g, + const AffineTransform& transform) const +{ + if (! isWhitespace()) { + g.getInternalContext()->setFont (font); + g.getInternalContext()->drawGlyph (glyph, AffineTransform::translation (x, y) + .followedBy (transform)); } +} - struct KerningPair +void PositionedGlyph::createPath (Path& path) const +{ + if (! isWhitespace()) { - juce_wchar character2; - float kerningAmount; - }; + Typeface* const t = font.getTypeface(); - void addKerningPair (const juce_wchar subsequentCharacter, - const float extraKerningAmount) throw() - { - KerningPair kp; - kp.character2 = subsequentCharacter; - kp.kerningAmount = extraKerningAmount; - kerningPairs.add (kp); + if (t != 0) + { + Path p; + t->getOutlineForGlyph (glyph, p); + + path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) + .translated (x, y)); + } } +} - float getHorizontalSpacing (const juce_wchar subsequentCharacter) const throw() +bool PositionedGlyph::hitTest (float px, float py) const +{ + if (getBounds().contains (px, py) && ! isWhitespace()) { - if (subsequentCharacter != 0) - { - for (int i = kerningPairs.size(); --i >= 0;) - if (kerningPairs.getReference(i).character2 == subsequentCharacter) - return width + kerningPairs.getReference(i).kerningAmount; - } + Typeface* const t = font.getTypeface(); - return width; - } + if (t != 0) + { + Path p; + t->getOutlineForGlyph (glyph, p); - const juce_wchar character; - const Path path; - float width; - Array kerningPairs; + AffineTransform::translation (-x, -y) + .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight()) + .transformPoint (px, py); - juce_UseDebuggingNewOperator + return p.contains (px, py); + } + } -private: - GlyphInfo (const GlyphInfo&); - GlyphInfo& operator= (const GlyphInfo&); -}; + return false; +} -CustomTypeface::CustomTypeface() - : Typeface (String::empty) +void PositionedGlyph::moveBy (const float deltaX, + const float deltaY) { - clear(); + x += deltaX; + y += deltaY; } -CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) - : Typeface (String::empty) +GlyphArrangement::GlyphArrangement() { - clear(); - - GZIPDecompressorInputStream gzin (&serialisedTypefaceStream, false); - BufferedInputStream in (&gzin, 32768, false); - - name = in.readString(); - isBold = in.readBool(); - isItalic = in.readBool(); - ascent = in.readFloat(); - defaultCharacter = (juce_wchar) in.readShort(); + glyphs.ensureStorageAllocated (128); +} - int i, numChars = in.readInt(); +GlyphArrangement::GlyphArrangement (const GlyphArrangement& other) +{ + addGlyphArrangement (other); +} - for (i = 0; i < numChars; ++i) +GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other) +{ + if (this != &other) { - const juce_wchar c = (juce_wchar) in.readShort(); - const float width = in.readFloat(); - - Path p; - p.loadPathFromStream (in); - addGlyph (c, p, width); + clear(); + addGlyphArrangement (other); } - const int numKerningPairs = in.readInt(); - - for (i = 0; i < numKerningPairs; ++i) - { - const juce_wchar char1 = (juce_wchar) in.readShort(); - const juce_wchar char2 = (juce_wchar) in.readShort(); - - addKerningPair (char1, char2, in.readFloat()); - } + return *this; } -CustomTypeface::~CustomTypeface() +GlyphArrangement::~GlyphArrangement() { } -void CustomTypeface::clear() +void GlyphArrangement::clear() { - defaultCharacter = 0; - ascent = 1.0f; - isBold = isItalic = false; - zeromem (lookupTable, sizeof (lookupTable)); glyphs.clear(); } -void CustomTypeface::setCharacteristics (const String& name_, const float ascent_, const bool isBold_, - const bool isItalic_, const juce_wchar defaultCharacter_) throw() +PositionedGlyph& GlyphArrangement::getGlyph (const int index) const { - name = name_; - defaultCharacter = defaultCharacter_; - ascent = ascent_; - isBold = isBold_; - isItalic = isItalic_; + jassert (((unsigned int) index) < (unsigned int) glyphs.size()); + + return *glyphs [index]; } -void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) throw() +void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other) { - // Check that you're not trying to add the same character twice.. - jassert (findGlyph (character, false) == 0); + glyphs.ensureStorageAllocated (glyphs.size() + other.glyphs.size()); + glyphs.addCopiesOf (other.glyphs); +} - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable)) - lookupTable [character] = (short) glyphs.size(); +void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num) +{ + glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num); +} - glyphs.add (new GlyphInfo (character, path, width)); +void GlyphArrangement::addLineOfText (const Font& font, + const String& text, + const float xOffset, + const float yOffset) +{ + addCurtailedLineOfText (font, text, + xOffset, yOffset, + 1.0e10f, false); } -void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) throw() +void GlyphArrangement::addCurtailedLineOfText (const Font& font, + const String& text, + float xOffset, + const float yOffset, + const float maxWidthPixels, + const bool useEllipsis) { - if (extraAmount != 0) + if (text.isNotEmpty()) { - GlyphInfo* const g = findGlyph (char1, true); - jassert (g != 0); // can only add kerning pairs for characters that exist! + Array newGlyphs; + Array xOffsets; + font.getGlyphPositions (text, newGlyphs, xOffsets); + const int textLen = newGlyphs.size(); - if (g != 0) - g->addKerningPair (char2, extraAmount); + const juce_wchar* const unicodeText = text; + + for (int i = 0; i < textLen; ++i) + { + const float thisX = xOffsets.getUnchecked (i); + const float nextX = xOffsets.getUnchecked (i + 1); + + if (nextX > maxWidthPixels + 1.0f) + { + // curtail the string if it's too wide.. + if (useEllipsis && textLen > 3 && glyphs.size() >= 3) + insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size()); + + break; + } + else + { + glyphs.add (new PositionedGlyph (xOffset + thisX, yOffset, nextX - thisX, + font, unicodeText[i], newGlyphs.getUnchecked(i))); + } + } } } -CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) throw() +int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos, + const int startIndex, int endIndex) { - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable) && lookupTable [character] > 0) - return glyphs [(int) lookupTable [(int) character]]; + int numDeleted = 0; - for (int i = 0; i < glyphs.size(); ++i) + if (glyphs.size() > 0) { - GlyphInfo* const g = glyphs.getUnchecked(i); - if (g->character == character) - return g; - } + Array dotGlyphs; + Array dotXs; + font.getGlyphPositions ("..", dotGlyphs, dotXs); - if (loadIfNeeded && loadGlyphIfPossible (character)) - return findGlyph (character, false); + const float dx = dotXs[1]; + float xOffset = 0.0f, yOffset = 0.0f; - return 0; -} + while (endIndex > startIndex) + { + const PositionedGlyph* pg = glyphs.getUnchecked (--endIndex); + xOffset = pg->x; + yOffset = pg->y; -CustomTypeface::GlyphInfo* CustomTypeface::findGlyphSubstituting (const juce_wchar character) throw() -{ - GlyphInfo* glyph = findGlyph (character, true); + glyphs.remove (endIndex); + ++numDeleted; - if (glyph == 0) - { - if (CharacterFunctions::isWhitespace (character) && character != L' ') - glyph = findGlyph (L' ', true); + if (xOffset + dx * 3 <= maxXPos) + break; + } - if (glyph == 0) + for (int i = 3; --i >= 0;) { - const Font fallbackFont (Font::getFallbackFontName(), 10, 0); - Typeface* const fallbackTypeface = fallbackFont.getTypeface(); - if (fallbackTypeface != 0 && fallbackTypeface != this) - { - //xxx - } + glyphs.insert (endIndex++, new PositionedGlyph (xOffset, yOffset, dx, + font, '.', dotGlyphs.getFirst())); + --numDeleted; + xOffset += dx; - if (glyph == 0) - glyph = findGlyph (defaultCharacter, true); + if (xOffset > maxXPos) + break; } } - return glyph; + return numDeleted; } -bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) +void GlyphArrangement::addJustifiedText (const Font& font, + const String& text, + float x, float y, + const float maxLineWidth, + const Justification& horizontalLayout) { - return false; -} + int lineStartIndex = glyphs.size(); + addLineOfText (font, text, x, y); -void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) throw() -{ - setCharacteristics (name, typefaceToCopy.getAscent(), isBold, isItalic, defaultCharacter); + const float originalY = y; - for (int i = 0; i < numCharacters; ++i) + while (lineStartIndex < glyphs.size()) { - const juce_wchar c = (juce_wchar) (characterStartIndex + i); + int i = lineStartIndex; - Array glyphIndexes; - Array offsets; - typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets); + if (glyphs.getUnchecked(i)->getCharacter() != '\n' + && glyphs.getUnchecked(i)->getCharacter() != '\r') + ++i; - const int glyphIndex = glyphIndexes.getFirst(); + const float lineMaxX = glyphs.getUnchecked (lineStartIndex)->getLeft() + maxLineWidth; + int lastWordBreakIndex = -1; - if (glyphIndex >= 0 && glyphIndexes.size() > 0) + while (i < glyphs.size()) { - const float glyphWidth = offsets[1]; + const PositionedGlyph* pg = glyphs.getUnchecked (i); + const juce_wchar c = pg->getCharacter(); - Path p; - typefaceToCopy.getOutlineForGlyph (glyphIndex, p); + if (c == '\r' || c == '\n') + { + ++i; - addGlyph (c, p, glyphWidth); + if (c == '\r' && i < glyphs.size() + && glyphs.getUnchecked(i)->getCharacter() == '\n') + ++i; - for (int j = glyphs.size() - 1; --j >= 0;) + break; + } + else if (pg->isWhitespace()) { - const juce_wchar char2 = glyphs.getUnchecked (j)->character; - glyphIndexes.clearQuick(); - offsets.clearQuick(); - typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets); + lastWordBreakIndex = i + 1; + } + else if (pg->getRight() - 0.0001f >= lineMaxX) + { + if (lastWordBreakIndex >= 0) + i = lastWordBreakIndex; - if (offsets.size() > 1) - addKerningPair (c, char2, offsets[1] - glyphWidth); + break; + } + + ++i; + } + + const float currentLineStartX = glyphs.getUnchecked (lineStartIndex)->getLeft(); + float currentLineEndX = currentLineStartX; + + for (int j = i; --j >= lineStartIndex;) + { + if (! glyphs.getUnchecked (j)->isWhitespace()) + { + currentLineEndX = glyphs.getUnchecked (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(); } } -bool CustomTypeface::writeToStream (OutputStream& outputStream) +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) { - GZIPCompressorOutputStream out (&outputStream); + // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 + jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); - out.writeString (name); - out.writeBool (isBold); - out.writeBool (isItalic); - out.writeFloat (ascent); - out.writeShort ((short) (unsigned short) defaultCharacter); - out.writeInt (glyphs.size()); + if (text.containsAnyOf ("\r\n")) + { + GlyphArrangement ga; + ga.addJustifiedText (f, text, x, y, width, layout); - int i, numKerningPairs = 0; + const Rectangle bb (ga.getBoundingBox (0, -1, false)); - for (i = 0; i < glyphs.size(); ++i) - { - const GlyphInfo* const g = glyphs.getUnchecked (i); - out.writeShort ((short) (unsigned short) g->character); - out.writeFloat (g->width); - g->path.writePathToStream (out); + float dy = y - bb.getY(); - numKerningPairs += g->kerningPairs.size(); + 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; } - out.writeInt (numKerningPairs); + int startIndex = glyphs.size(); + addLineOfText (f, text.trim(), x, y); - for (i = 0; i < glyphs.size(); ++i) + if (glyphs.size() > startIndex) { - const GlyphInfo* const g = glyphs.getUnchecked (i); + float lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() + - glyphs.getUnchecked (startIndex)->getLeft(); - for (int j = 0; j < g->kerningPairs.size(); ++j) + if (lineWidth <= 0) + return; + + if (lineWidth * minimumHorizontalScale < width) { - const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j); - out.writeShort ((short) (unsigned short) g->character); - out.writeShort ((short) (unsigned short) p.character2); - out.writeFloat (p.kerningAmount); + 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; - return true; -} + if (length <= 12 && ! txt.containsAnyOf (" -\t\r\n")) + maximumLines = 1; -float CustomTypeface::getAscent() const -{ - return ascent; -} + maximumLines = jmin (maximumLines, length); -float CustomTypeface::getDescent() const -{ - return 1.0f - ascent; + 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); + } + } } -float CustomTypeface::getStringWidth (const String& text) +void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, + const float dx, const float dy) { - float x = 0; - const juce_wchar* t = text; + jassert (startIndex >= 0); - while (*t != 0) + if (dx != 0.0f || dy != 0.0f) { - const GlyphInfo* const glyph = findGlyphSubstituting (*t++); + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; - if (glyph != 0) - x += glyph->getHorizontalSpacing (*t); + while (--num >= 0) + glyphs.getUnchecked (startIndex++)->moveBy (dx, dy); } - - return x; } -void CustomTypeface::getGlyphPositions (const String& text, Array & resultGlyphs, Array& xOffsets) +int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, + const Justification& justification, float minimumHorizontalScale) { - xOffsets.add (0); - float x = 0; - const juce_wchar* t = text; + int numDeleted = 0; + const float lineStartX = glyphs.getUnchecked (start)->getLeft(); + float lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX; - while (*t != 0) + if (lineWidth > w) { - const juce_wchar c = *t++; - const GlyphInfo* const glyph = findGlyphSubstituting (c); + if (minimumHorizontalScale < 1.0f) + { + stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth)); + lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX - 0.5f; + } - if (glyph != 0) + if (lineWidth > w) { - x += glyph->getHorizontalSpacing (*t); - resultGlyphs.add ((int) glyph->character); - xOffsets.add (x); + numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs); + numGlyphs -= numDeleted; } } + + justifyGlyphs (start, numGlyphs, x, y, w, h, justification); + return numDeleted; } -bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) +void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, + const float horizontalScaleFactor) { - const GlyphInfo* const glyph = findGlyphSubstituting ((juce_wchar) glyphNumber); - if (glyph != 0) - { - path = glyph->path; - return true; - } - - return false; -} + jassert (startIndex >= 0); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Typeface.cpp ***/ + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; + if (num > 0) + { + const float xAnchor = glyphs.getUnchecked (startIndex)->getLeft(); -/*** Start of inlined file: juce_AffineTransform.cpp ***/ -BEGIN_JUCE_NAMESPACE + while (--num >= 0) + { + PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++); -AffineTransform::AffineTransform() throw() - : mat00 (1.0f), - mat01 (0), - mat02 (0), - mat10 (0), - mat11 (1.0f), - mat12 (0) -{ + pg->x = xAnchor + (pg->x - xAnchor) * horizontalScaleFactor; + pg->font.setHorizontalScale (pg->font.getHorizontalScale() * horizontalScaleFactor); + pg->w *= horizontalScaleFactor; + } + } } -AffineTransform::AffineTransform (const AffineTransform& other) throw() - : mat00 (other.mat00), - mat01 (other.mat01), - mat02 (other.mat02), - mat10 (other.mat10), - mat11 (other.mat11), - mat12 (other.mat12) +const Rectangle GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const { -} + jassert (startIndex >= 0); -AffineTransform::AffineTransform (const float mat00_, - const float mat01_, - const float mat02_, - const float mat10_, - const float mat11_, - const float mat12_) throw() - : mat00 (mat00_), - mat01 (mat01_), - mat02 (mat02_), - mat10 (mat10_), - mat11 (mat11_), - mat12 (mat12_) -{ -} + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; -AffineTransform& AffineTransform::operator= (const AffineTransform& other) throw() -{ - mat00 = other.mat00; - mat01 = other.mat01; - mat02 = other.mat02; - mat10 = other.mat10; - mat11 = other.mat11; - mat12 = other.mat12; + Rectangle result; - return *this; -} + while (--num >= 0) + { + const PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++); -bool AffineTransform::operator== (const AffineTransform& other) const throw() -{ - return mat00 == other.mat00 - && mat01 == other.mat01 - && mat02 == other.mat02 - && mat10 == other.mat10 - && mat11 == other.mat11 - && mat12 == other.mat12; -} + if (includeWhitespace || ! pg->isWhitespace()) + result = result.getUnion (pg->getBounds()); + } -bool AffineTransform::operator!= (const AffineTransform& other) const throw() -{ - return ! operator== (other); + return result; } -bool AffineTransform::isIdentity() const throw() +void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, + const float x, const float y, const float width, const float height, + const Justification& justification) { - return (mat01 == 0) - && (mat02 == 0) - && (mat10 == 0) - && (mat12 == 0) - && (mat00 == 1.0f) - && (mat11 == 1.0f); -} + jassert (num >= 0 && startIndex >= 0); -const AffineTransform AffineTransform::identity; + if (glyphs.size() > 0 && num > 0) + { + const Rectangle bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified + | Justification::horizontallyCentred))); + float deltaX = 0.0f; -const AffineTransform AffineTransform::followedBy (const AffineTransform& other) const throw() -{ - return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10, - other.mat00 * mat01 + other.mat01 * mat11, - other.mat00 * mat02 + other.mat01 * mat12 + other.mat02, - other.mat10 * mat00 + other.mat11 * mat10, - other.mat10 * mat01 + other.mat11 * mat11, - other.mat10 * mat02 + other.mat11 * mat12 + other.mat12); -} + 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(); -const AffineTransform AffineTransform::followedBy (const float omat00, - const float omat01, - const float omat02, - const float omat10, - const float omat11, - const float omat12) const throw() -{ - return AffineTransform (omat00 * mat00 + omat01 * mat10, - omat00 * mat01 + omat01 * mat11, - omat00 * mat02 + omat01 * mat12 + omat02, - omat10 * mat00 + omat11 * mat10, - omat10 * mat01 + omat11 * mat11, - omat10 * mat02 + omat11 * mat12 + omat12); -} + float deltaY = 0.0f; -const AffineTransform AffineTransform::translated (const float dx, - const float dy) const throw() -{ - return AffineTransform (mat00, mat01, mat02 + dx, - mat10, mat11, mat12 + dy); -} + 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(); -const AffineTransform AffineTransform::translation (const float dx, - const float dy) throw() -{ - return AffineTransform (1.0f, 0, dx, - 0, 1.0f, dy); -} + moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); -const AffineTransform AffineTransform::rotated (const float rad) const throw() -{ - const float cosRad = std::cos (rad); - const float sinRad = std::sin (rad); + if (justification.testFlags (Justification::horizontallyJustified)) + { + int lineStart = 0; + float baseY = glyphs.getUnchecked (startIndex)->getBaselineY(); - return followedBy (cosRad, -sinRad, 0, - sinRad, cosRad, 0); -} + int i; + for (i = 0; i < num; ++i) + { + const float glyphY = glyphs.getUnchecked (startIndex + i)->getBaselineY(); -const AffineTransform AffineTransform::rotation (const float rad) throw() -{ - const float cosRad = std::cos (rad); - const float sinRad = std::sin (rad); + if (glyphY != baseY) + { + spreadOutLine (startIndex + lineStart, i - lineStart, width); - return AffineTransform (cosRad, -sinRad, 0, - sinRad, cosRad, 0); -} + lineStart = i; + baseY = glyphY; + } + } -const AffineTransform AffineTransform::rotated (const float angle, - const float pivotX, - const float pivotY) const throw() -{ - return translated (-pivotX, -pivotY) - .rotated (angle) - .translated (pivotX, pivotY); + if (i > lineStart) + spreadOutLine (startIndex + lineStart, i - lineStart, width); + } + } } -const AffineTransform AffineTransform::rotation (const float angle, - const float pivotX, - const float pivotY) throw() +void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth) { - return translation (-pivotX, -pivotY) - .rotated (angle) - .translated (pivotX, pivotY); -} + if (start + num < glyphs.size() + && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\r' + && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\n') + { + int numSpaces = 0; + int spacesAtEnd = 0; -const AffineTransform AffineTransform::scaled (const float factorX, - const float factorY) const throw() -{ - return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02, - factorY * mat10, factorY * mat11, factorY * mat12); -} + for (int i = 0; i < num; ++i) + { + if (glyphs.getUnchecked (start + i)->isWhitespace()) + { + ++spacesAtEnd; + ++numSpaces; + } + else + { + spacesAtEnd = 0; + } + } -const AffineTransform AffineTransform::scale (const float factorX, - const float factorY) throw() -{ - return AffineTransform (factorX, 0, 0, - 0, factorY, 0); -} + numSpaces -= spacesAtEnd; -const AffineTransform AffineTransform::sheared (const float shearX, - const float shearY) const throw() -{ - return followedBy (1.0f, shearX, 0, - shearY, 1.0f, 0); -} + if (numSpaces > 0) + { + const float startX = glyphs.getUnchecked (start)->getLeft(); + const float endX = glyphs.getUnchecked (start + num - 1 - spacesAtEnd)->getRight(); -const AffineTransform AffineTransform::inverted() const throw() -{ - double determinant = (mat00 * mat11 - mat10 * mat01); + const float extraPaddingBetweenWords + = (targetWidth - (endX - startX)) / (float) numSpaces; - if (determinant != 0.0) - { - determinant = 1.0 / determinant; + float deltaX = 0.0f; - const float dst00 = (float) (mat11 * determinant); - const float dst10 = (float) (-mat10 * determinant); - const float dst01 = (float) (-mat01 * determinant); - const float dst11 = (float) (mat00 * determinant); + for (int i = 0; i < num; ++i) + { + glyphs.getUnchecked (start + i)->moveBy (deltaX, 0.0f); - return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01, - dst10, dst11, -mat02 * dst10 - mat12 * dst11); - } - else - { - // singularity.. - return *this; + if (glyphs.getUnchecked (start + i)->isWhitespace()) + deltaX += extraPaddingBetweenWords; + } + } } } -bool AffineTransform::isSingularity() const throw() -{ - return (mat00 * mat11 - mat10 * mat01) == 0.0; -} - -const AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00, - const float x10, const float y10, - const float x01, const float y01) throw() -{ - return AffineTransform (x10 - x00, x01 - x00, x00, - y10 - y00, y01 - y00, y00); -} - -const AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, - const float sx2, const float sy2, const float tx2, const float ty2, - const float sx3, const float sy3, const float tx3, const float ty3) throw() -{ - return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) - .inverted() - .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); -} - -bool AffineTransform::isOnlyTranslation() const throw() +void GlyphArrangement::draw (const Graphics& g) const { - return (mat01 == 0) - && (mat10 == 0) - && (mat00 == 1.0f) - && (mat11 == 1.0f); -} - -END_JUCE_NAMESPACE -/*** End of inlined file: juce_AffineTransform.cpp ***/ - - -/*** Start of inlined file: juce_BorderSize.cpp ***/ -BEGIN_JUCE_NAMESPACE + for (int i = 0; i < glyphs.size(); ++i) + { + const PositionedGlyph* const pg = glyphs.getUnchecked(i); -BorderSize::BorderSize() throw() - : top (0), - left (0), - bottom (0), - right (0) -{ -} + if (pg->font.isUnderlined()) + { + const float lineThickness = (pg->font.getDescent()) * 0.3f; -BorderSize::BorderSize (const BorderSize& other) throw() - : top (other.top), - left (other.left), - bottom (other.bottom), - right (other.right) -{ -} + float nextX = pg->x + pg->w; -BorderSize::BorderSize (const int topGap, - const int leftGap, - const int bottomGap, - const int rightGap) throw() - : top (topGap), - left (leftGap), - bottom (bottomGap), - right (rightGap) -{ -} + if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y) + nextX = glyphs.getUnchecked (i + 1)->x; -BorderSize::BorderSize (const int allGaps) throw() - : top (allGaps), - left (allGaps), - bottom (allGaps), - right (allGaps) -{ -} + g.fillRect (pg->x, pg->y + lineThickness * 2.0f, + nextX - pg->x, lineThickness); + } -BorderSize::~BorderSize() throw() -{ + pg->draw (g); + } } -void BorderSize::setTop (const int newTopGap) throw() +void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const { - top = newTopGap; -} + for (int i = 0; i < glyphs.size(); ++i) + { + const PositionedGlyph* const pg = glyphs.getUnchecked(i); -void BorderSize::setLeft (const int newLeftGap) throw() -{ - left = newLeftGap; -} + if (pg->font.isUnderlined()) + { + const float lineThickness = (pg->font.getDescent()) * 0.3f; -void BorderSize::setBottom (const int newBottomGap) throw() -{ - bottom = newBottomGap; -} + float nextX = pg->x + pg->w; -void BorderSize::setRight (const int newRightGap) throw() -{ - right = newRightGap; -} + if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y) + nextX = glyphs.getUnchecked (i + 1)->x; -const Rectangle BorderSize::subtractedFrom (const Rectangle& r) const throw() -{ - return Rectangle (r.getX() + left, - r.getY() + top, - r.getWidth() - (left + right), - r.getHeight() - (top + bottom)); -} + Path p; + p.addLineSegment (Line (pg->x, pg->y + lineThickness * 2.0f, + nextX, pg->y + lineThickness * 2.0f), + lineThickness); -void BorderSize::subtractFrom (Rectangle& r) const throw() -{ - r.setBounds (r.getX() + left, - r.getY() + top, - r.getWidth() - (left + right), - r.getHeight() - (top + bottom)); -} + g.fillPath (p, transform); + } -const Rectangle BorderSize::addedTo (const Rectangle& r) const throw() -{ - return Rectangle (r.getX() - left, - r.getY() - top, - r.getWidth() + (left + right), - r.getHeight() + (top + bottom)); + pg->draw (g, transform); + } } -void BorderSize::addTo (Rectangle& r) const throw() +void GlyphArrangement::createPath (Path& path) const { - r.setBounds (r.getX() - left, - r.getY() - top, - r.getWidth() + (left + right), - r.getHeight() + (top + bottom)); + for (int i = 0; i < glyphs.size(); ++i) + glyphs.getUnchecked (i)->createPath (path); } -bool BorderSize::operator== (const BorderSize& other) const throw() +int GlyphArrangement::findGlyphIndexAt (float x, float y) const { - return top == other.top - && left == other.left - && bottom == other.bottom - && right == other.right; -} + for (int i = 0; i < glyphs.size(); ++i) + if (glyphs.getUnchecked (i)->hitTest (x, y)) + return i; -bool BorderSize::operator!= (const BorderSize& other) const throw() -{ - return ! operator== (other); + return -1; } END_JUCE_NAMESPACE -/*** End of inlined file: juce_BorderSize.cpp ***/ +/*** End of inlined file: juce_GlyphArrangement.cpp ***/ -/*** Start of inlined file: juce_Path.cpp ***/ +/*** Start of inlined file: juce_TextLayout.cpp ***/ BEGIN_JUCE_NAMESPACE -// tests that some co-ords aren't NaNs -#define CHECK_COORDS_ARE_VALID(x, y) \ - jassert (x == x && y == y); - -namespace PathHelpers +class TextLayout::Token { - static const float ellipseAngularIncrement = 0.05f; +public: + String text; + Font font; + int x, y, w, h; + int line, lineHeight; + bool isWhitespace, isNewLine; - static const String nextToken (const juce_wchar*& t) + Token (const String& t, + const Font& f, + const bool isWhitespace_) + : text (t), + font (f), + x(0), + y(0), + isWhitespace (isWhitespace_) { - while (CharacterFunctions::isWhitespace (*t)) - ++t; + w = font.getStringWidth (t); + h = roundToInt (f.getHeight()); + isNewLine = t.containsChar ('\n') || t.containsChar ('\r'); + } - const juce_wchar* const start = t; + Token (const Token& other) + : text (other.text), + font (other.font), + x (other.x), + y (other.y), + w (other.w), + h (other.h), + line (other.line), + lineHeight (other.lineHeight), + isWhitespace (other.isWhitespace), + isNewLine (other.isNewLine) + { + } - while (*t != 0 && ! CharacterFunctions::isWhitespace (*t)) - ++t; + ~Token() + { + } - return String (start, (int) (t - start)); + void draw (Graphics& g, + const int xOffset, + const int yOffset) + { + if (! isWhitespace) + { + g.setFont (font); + g.drawSingleLineText (text.trimEnd(), + xOffset + x, + yOffset + y + (lineHeight - h) + + roundToInt (font.getAscent())); + } } -} -const float Path::lineMarker = 100001.0f; -const float Path::moveMarker = 100002.0f; -const float Path::quadMarker = 100003.0f; -const float Path::cubicMarker = 100004.0f; -const float Path::closeSubPathMarker = 100005.0f; + juce_UseDebuggingNewOperator +}; -Path::Path() - : numElements (0), - pathXMin (0), - pathXMax (0), - pathYMin (0), - pathYMax (0), - useNonZeroWinding (true) +TextLayout::TextLayout() + : totalLines (0) { + tokens.ensureStorageAllocated (64); } -Path::~Path() +TextLayout::TextLayout (const String& text, const Font& font) + : totalLines (0) { + tokens.ensureStorageAllocated (64); + appendText (text, font); } -Path::Path (const Path& other) - : numElements (other.numElements), - pathXMin (other.pathXMin), - pathXMax (other.pathXMax), - pathYMin (other.pathYMin), - pathYMax (other.pathYMax), - useNonZeroWinding (other.useNonZeroWinding) +TextLayout::TextLayout (const TextLayout& other) + : totalLines (0) { - if (numElements > 0) - { - data.setAllocatedSize ((int) numElements); - memcpy (data.elements, other.data.elements, numElements * sizeof (float)); - } + *this = other; } -Path& Path::operator= (const Path& other) +TextLayout& TextLayout::operator= (const TextLayout& other) { if (this != &other) { - data.ensureAllocatedSize ((int) other.numElements); - - numElements = other.numElements; - pathXMin = other.pathXMin; - pathXMax = other.pathXMax; - pathYMin = other.pathYMin; - pathYMax = other.pathYMax; - useNonZeroWinding = other.useNonZeroWinding; + clear(); - if (numElements > 0) - memcpy (data.elements, other.data.elements, numElements * sizeof (float)); + totalLines = other.totalLines; + tokens.addCopiesOf (other.tokens); } return *this; } -bool Path::operator== (const Path& other) const throw() -{ - return ! operator!= (other); -} - -bool Path::operator!= (const Path& other) const throw() +TextLayout::~TextLayout() { - if (numElements != other.numElements || useNonZeroWinding != other.useNonZeroWinding) - return true; - - for (size_t i = 0; i < numElements; ++i) - if (data.elements[i] != other.data.elements[i]) - return true; - - return false; + clear(); } -void Path::clear() throw() +void TextLayout::clear() { - numElements = 0; - pathXMin = 0; - pathYMin = 0; - pathYMax = 0; - pathXMax = 0; + tokens.clear(); + totalLines = 0; } -void Path::swapWithPath (Path& other) throw() +void TextLayout::appendText (const String& text, const Font& font) { - data.swapWith (other.data); - swapVariables (numElements, other.numElements); - swapVariables (pathXMin, other.pathXMin); - swapVariables (pathXMax, other.pathXMax); - swapVariables (pathYMin, other.pathYMin); - swapVariables (pathYMax, other.pathYMax); - swapVariables (useNonZeroWinding, other.useNonZeroWinding); -} + const juce_wchar* t = text; + String currentString; + int lastCharType = 0; -void Path::setUsingNonZeroWinding (const bool isNonZero) throw() -{ - useNonZeroWinding = isNonZero; -} + for (;;) + { + const juce_wchar c = *t++; + if (c == 0) + break; -void Path::scaleToFit (const float x, const float y, const float w, const float h, - const bool preserveProportions) throw() -{ - applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions)); -} + int charType; + if (c == '\r' || c == '\n') + { + charType = 0; + } + else if (CharacterFunctions::isWhitespace (c)) + { + charType = 2; + } + else + { + charType = 1; + } -bool Path::isEmpty() const throw() -{ - size_t i = 0; + if (charType == 0 || charType != lastCharType) + { + if (currentString.isNotEmpty()) + { + tokens.add (new Token (currentString, font, + lastCharType == 2 || lastCharType == 0)); + } - while (i < numElements) - { - const float type = data.elements [i++]; + currentString = String::charToString (c); - if (type == moveMarker) - { - i += 2; + if (c == '\r' && *t == '\n') + currentString += *t++; } - else if (type == lineMarker - || type == quadMarker - || type == cubicMarker) + else { - return false; + currentString += c; } - } - return true; -} + lastCharType = charType; + } -const Rectangle Path::getBounds() const throw() -{ - return Rectangle (pathXMin, pathYMin, - pathXMax - pathXMin, - pathYMax - pathYMin); + if (currentString.isNotEmpty()) + tokens.add (new Token (currentString, font, lastCharType == 2)); } -const Rectangle Path::getBoundsTransformed (const AffineTransform& transform) const throw() +void TextLayout::setText (const String& text, const Font& font) { - return getBounds().transformed (transform); + clear(); + appendText (text, font); } -void Path::startNewSubPath (const float x, const float y) +void TextLayout::layout (int maxWidth, + const Justification& justification, + const bool attemptToBalanceLineLengths) { - CHECK_COORDS_ARE_VALID (x, y); - - if (numElements == 0) - { - pathXMin = pathXMax = x; - pathYMin = pathYMax = y; - } - else + if (attemptToBalanceLineLengths) { - pathXMin = jmin (pathXMin, x); - pathXMax = jmax (pathXMax, x); - pathYMin = jmin (pathYMin, y); - pathYMax = jmax (pathYMax, y); - } + const int originalW = maxWidth; + int bestWidth = maxWidth; + float bestLineProportion = 0.0f; - data.ensureAllocatedSize ((int) numElements + 3); + while (maxWidth > originalW / 2) + { + layout (maxWidth, justification, false); - data.elements [numElements++] = moveMarker; - data.elements [numElements++] = x; - data.elements [numElements++] = y; -} + if (getNumLines() <= 1) + return; -void Path::startNewSubPath (const Point& start) -{ - startNewSubPath (start.getX(), start.getY()); -} + const int lastLineW = getLineWidth (getNumLines() - 1); + const int lastButOneLineW = getLineWidth (getNumLines() - 2); -void Path::lineTo (const float x, const float y) -{ - CHECK_COORDS_ARE_VALID (x, y); + const float prop = lastLineW / (float) lastButOneLineW; - if (numElements == 0) - startNewSubPath (0, 0); + if (prop > 0.9f) + return; - data.ensureAllocatedSize ((int) numElements + 3); + if (prop > bestLineProportion) + { + bestLineProportion = prop; + bestWidth = maxWidth; + } - data.elements [numElements++] = lineMarker; - data.elements [numElements++] = x; - data.elements [numElements++] = y; + maxWidth -= 10; + } - pathXMin = jmin (pathXMin, x); - pathXMax = jmax (pathXMax, x); - pathYMin = jmin (pathYMin, y); - pathYMax = jmax (pathYMax, y); -} + layout (bestWidth, justification, false); + } + else + { + int x = 0; + int y = 0; + int h = 0; + totalLines = 0; + int i; -void Path::lineTo (const Point& end) -{ - lineTo (end.getX(), end.getY()); -} + for (i = 0; i < tokens.size(); ++i) + { + Token* const t = tokens.getUnchecked(i); + t->x = x; + t->y = y; + t->line = totalLines; + x += t->w; + h = jmax (h, t->h); -void Path::quadraticTo (const float x1, const float y1, - const float x2, const float y2) -{ - CHECK_COORDS_ARE_VALID (x1, y1); - CHECK_COORDS_ARE_VALID (x2, y2); + const Token* nextTok = tokens [i + 1]; - if (numElements == 0) - startNewSubPath (0, 0); + if (nextTok == 0) + break; - data.ensureAllocatedSize ((int) numElements + 5); + if (t->isNewLine || ((! nextTok->isWhitespace) && x + nextTok->w > maxWidth)) + { + // finished a line, so go back and update the heights of the things on it + for (int j = i; j >= 0; --j) + { + Token* const tok = tokens.getUnchecked(j); - data.elements [numElements++] = quadMarker; - data.elements [numElements++] = x1; - data.elements [numElements++] = y1; - data.elements [numElements++] = x2; - data.elements [numElements++] = y2; + if (tok->line == totalLines) + tok->lineHeight = h; + else + break; + } - pathXMin = jmin (pathXMin, x1, x2); - pathXMax = jmax (pathXMax, x1, x2); - pathYMin = jmin (pathYMin, y1, y2); - pathYMax = jmax (pathYMax, y1, y2); -} + x = 0; + y += h; + h = 0; + ++totalLines; + } + } -void Path::quadraticTo (const Point& controlPoint, - const Point& endPoint) -{ - quadraticTo (controlPoint.getX(), controlPoint.getY(), - endPoint.getX(), endPoint.getY()); -} + // finished a line, so go back and update the heights of the things on it + for (int j = jmin (i, tokens.size() - 1); j >= 0; --j) + { + Token* const t = tokens.getUnchecked(j); -void Path::cubicTo (const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3) -{ - CHECK_COORDS_ARE_VALID (x1, y1); - CHECK_COORDS_ARE_VALID (x2, y2); - CHECK_COORDS_ARE_VALID (x3, y3); + if (t->line == totalLines) + t->lineHeight = h; + else + break; + } - if (numElements == 0) - startNewSubPath (0, 0); + ++totalLines; - data.ensureAllocatedSize ((int) numElements + 7); + if (! justification.testFlags (Justification::left)) + { + int totalW = getWidth(); - data.elements [numElements++] = cubicMarker; - data.elements [numElements++] = x1; - data.elements [numElements++] = y1; - data.elements [numElements++] = x2; - data.elements [numElements++] = y2; - data.elements [numElements++] = x3; - data.elements [numElements++] = y3; + for (i = totalLines; --i >= 0;) + { + const int lineW = getLineWidth (i); - pathXMin = jmin (pathXMin, x1, x2, x3); - pathXMax = jmax (pathXMax, x1, x2, x3); - pathYMin = jmin (pathYMin, y1, y2, y3); - pathYMax = jmax (pathYMax, y1, y2, y3); -} + int dx = 0; + if (justification.testFlags (Justification::horizontallyCentred)) + dx = (totalW - lineW) / 2; + else if (justification.testFlags (Justification::right)) + dx = totalW - lineW; -void Path::cubicTo (const Point& controlPoint1, - const Point& controlPoint2, - const Point& endPoint) -{ - cubicTo (controlPoint1.getX(), controlPoint1.getY(), - controlPoint2.getX(), controlPoint2.getY(), - endPoint.getX(), endPoint.getY()); -} + for (int j = tokens.size(); --j >= 0;) + { + Token* const t = tokens.getUnchecked(j); -void Path::closeSubPath() -{ - if (numElements > 0 - && data.elements [numElements - 1] != closeSubPathMarker) - { - data.ensureAllocatedSize ((int) numElements + 1); - data.elements [numElements++] = closeSubPathMarker; + if (t->line == i) + t->x += dx; + } + } + } } } -const Point Path::getCurrentPosition() const +int TextLayout::getLineWidth (const int lineNumber) const { - size_t i = numElements - 1; + int maxW = 0; - if (i > 0 && data.elements[i] == closeSubPathMarker) + for (int i = tokens.size(); --i >= 0;) { - while (i >= 0) - { - if (data.elements[i] == moveMarker) - { - i += 2; - break; - } + const Token* const t = tokens.getUnchecked(i); - --i; - } + if (t->line == lineNumber && ! t->isWhitespace) + maxW = jmax (maxW, t->x + t->w); } - if (i > 0) - return Point (data.elements [i - 1], data.elements [i]); - - return Point(); + return maxW; } -void Path::addRectangle (const float x, const float y, - const float w, const float h) +int TextLayout::getWidth() const { - float x1 = x, y1 = y, x2 = x + w, y2 = y + h; - - if (w < 0) - swapVariables (x1, x2); - - if (h < 0) - swapVariables (y1, y2); - - data.ensureAllocatedSize ((int) numElements + 13); + int maxW = 0; - if (numElements == 0) - { - pathXMin = x1; - pathXMax = x2; - pathYMin = y1; - pathYMax = y2; - } - else + for (int i = tokens.size(); --i >= 0;) { - pathXMin = jmin (pathXMin, x1); - pathXMax = jmax (pathXMax, x2); - pathYMin = jmin (pathYMin, y1); - pathYMax = jmax (pathYMax, y2); + const Token* const t = tokens.getUnchecked(i); + if (! t->isWhitespace) + maxW = jmax (maxW, t->x + t->w); } - data.elements [numElements++] = moveMarker; - data.elements [numElements++] = x1; - data.elements [numElements++] = y2; - data.elements [numElements++] = lineMarker; - data.elements [numElements++] = x1; - data.elements [numElements++] = y1; - data.elements [numElements++] = lineMarker; - data.elements [numElements++] = x2; - data.elements [numElements++] = y1; - data.elements [numElements++] = lineMarker; - data.elements [numElements++] = x2; - data.elements [numElements++] = y2; - data.elements [numElements++] = closeSubPathMarker; -} - -void Path::addRectangle (const Rectangle& rectangle) -{ - addRectangle ((float) rectangle.getX(), (float) rectangle.getY(), - (float) rectangle.getWidth(), (float) rectangle.getHeight()); + return maxW; } -void Path::addRoundedRectangle (const float x, const float y, - const float w, const float h, - float csx, - float csy) +int TextLayout::getHeight() const { - csx = jmin (csx, w * 0.5f); - csy = jmin (csy, h * 0.5f); - const float cs45x = csx * 0.45f; - const float cs45y = csy * 0.45f; - const float x2 = x + w; - const float y2 = y + h; + int maxH = 0; - startNewSubPath (x + csx, y); - lineTo (x2 - csx, y); - cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); - lineTo (x2, y2 - csy); - cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); - lineTo (x + csx, y2); - cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); - lineTo (x, y + csy); - cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); - closeSubPath(); -} + for (int i = tokens.size(); --i >= 0;) + { + const Token* const t = tokens.getUnchecked(i); -void Path::addRoundedRectangle (const float x, const float y, - const float w, const float h, - float cs) -{ - addRoundedRectangle (x, y, w, h, cs, cs); -} + if (! t->isWhitespace) + maxH = jmax (maxH, t->y + t->h); + } -void Path::addTriangle (const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3) -{ - startNewSubPath (x1, y1); - lineTo (x2, y2); - lineTo (x3, y3); - closeSubPath(); + return maxH; } -void Path::addQuadrilateral (const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3, - const float x4, const float y4) +void TextLayout::draw (Graphics& g, + const int xOffset, + const int yOffset) const { - startNewSubPath (x1, y1); - lineTo (x2, y2); - lineTo (x3, y3); - lineTo (x4, y4); - closeSubPath(); + for (int i = tokens.size(); --i >= 0;) + tokens.getUnchecked(i)->draw (g, xOffset, yOffset); } -void Path::addEllipse (const float x, const float y, - const float w, const float h) +void TextLayout::drawWithin (Graphics& g, + int x, int y, int w, int h, + const Justification& justification) const { - const float hw = w * 0.5f; - const float hw55 = hw * 0.55f; - const float hh = h * 0.5f; - const float hh45 = hh * 0.55f; - const float cx = x + hw; - const float cy = y + hh; + justification.applyToRectangle (x, y, getWidth(), getHeight(), + x, y, w, h); - startNewSubPath (cx, cy - hh); - cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh45, cx + hw, cy); - cubicTo (cx + hw, cy + hh45, cx + hw55, cy + hh, cx, cy + hh); - cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh45, cx - hw, cy); - cubicTo (cx - hw, cy - hh45, cx - hw55, cy - hh, cx, cy - hh); - closeSubPath(); + draw (g, x, y); } -void Path::addArc (const float x, const float y, - const float w, const float h, - const float fromRadians, - const float toRadians, - const bool startAsNewSubPath) +END_JUCE_NAMESPACE +/*** End of inlined file: juce_TextLayout.cpp ***/ + + +/*** Start of inlined file: juce_Typeface.cpp ***/ +BEGIN_JUCE_NAMESPACE + +Typeface::Typeface (const String& name_) throw() + : name (name_) { - const float radiusX = w / 2.0f; - const float radiusY = h / 2.0f; +} - addCentredArc (x + radiusX, - y + radiusY, - radiusX, radiusY, - 0.0f, - fromRadians, toRadians, - startAsNewSubPath); +Typeface::~Typeface() +{ } -void Path::addCentredArc (const float centreX, const float centreY, - const float radiusX, const float radiusY, - const float rotationOfEllipse, - const float fromRadians, - const float toRadians, - const bool startAsNewSubPath) +class CustomTypeface::GlyphInfo { - if (radiusX > 0.0f && radiusY > 0.0f) +public: + GlyphInfo (const juce_wchar character_, const Path& path_, const float width_) throw() + : character (character_), path (path_), width (width_) { - const Point centre (centreX, centreY); - const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); - float angle = fromRadians; + } - if (startAsNewSubPath) - startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + ~GlyphInfo() throw() + { + } - if (fromRadians < toRadians) - { - if (startAsNewSubPath) - angle += PathHelpers::ellipseAngularIncrement; + struct KerningPair + { + juce_wchar character2; + float kerningAmount; + }; - while (angle < toRadians) - { - lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); - angle += PathHelpers::ellipseAngularIncrement; - } - } - else - { - if (startAsNewSubPath) - angle -= PathHelpers::ellipseAngularIncrement; + void addKerningPair (const juce_wchar subsequentCharacter, + const float extraKerningAmount) throw() + { + KerningPair kp; + kp.character2 = subsequentCharacter; + kp.kerningAmount = extraKerningAmount; + kerningPairs.add (kp); + } - while (angle > toRadians) - { - lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); - angle -= PathHelpers::ellipseAngularIncrement; - } + float getHorizontalSpacing (const juce_wchar subsequentCharacter) const throw() + { + if (subsequentCharacter != 0) + { + for (int i = kerningPairs.size(); --i >= 0;) + if (kerningPairs.getReference(i).character2 == subsequentCharacter) + return width + kerningPairs.getReference(i).kerningAmount; } - lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation)); + return width; } + + const juce_wchar character; + const Path path; + float width; + Array kerningPairs; + + juce_UseDebuggingNewOperator + +private: + GlyphInfo (const GlyphInfo&); + GlyphInfo& operator= (const GlyphInfo&); +}; + +CustomTypeface::CustomTypeface() + : Typeface (String::empty) +{ + clear(); } -void Path::addPieSegment (const float x, const float y, - const float width, const float height, - const float fromRadians, - const float toRadians, - const float innerCircleProportionalSize) +CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) + : Typeface (String::empty) { - float radiusX = width * 0.5f; - float radiusY = height * 0.5f; - const Point centre (x + radiusX, y + radiusY); + clear(); - startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians)); - addArc (x, y, width, height, fromRadians, toRadians); + GZIPDecompressorInputStream gzin (&serialisedTypefaceStream, false); + BufferedInputStream in (&gzin, 32768, false); - if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f) - { - closeSubPath(); + name = in.readString(); + isBold = in.readBool(); + isItalic = in.readBool(); + ascent = in.readFloat(); + defaultCharacter = (juce_wchar) in.readShort(); - if (innerCircleProportionalSize > 0) - { - radiusX *= innerCircleProportionalSize; - radiusY *= innerCircleProportionalSize; + int i, numChars = in.readInt(); - startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians)); - addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); - } + for (i = 0; i < numChars; ++i) + { + const juce_wchar c = (juce_wchar) in.readShort(); + const float width = in.readFloat(); + + Path p; + p.loadPathFromStream (in); + addGlyph (c, p, width); } - else + + const int numKerningPairs = in.readInt(); + + for (i = 0; i < numKerningPairs; ++i) { - if (innerCircleProportionalSize > 0) - { - radiusX *= innerCircleProportionalSize; - radiusY *= innerCircleProportionalSize; + const juce_wchar char1 = (juce_wchar) in.readShort(); + const juce_wchar char2 = (juce_wchar) in.readShort(); - addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); - } - else - { - lineTo (centre); - } + addKerningPair (char1, char2, in.readFloat()); } +} - closeSubPath(); +CustomTypeface::~CustomTypeface() +{ } -void Path::addLineSegment (const Line& line, float lineThickness) +void CustomTypeface::clear() { - const Line reversed (line.reversed()); - lineThickness *= 0.5f; + defaultCharacter = 0; + ascent = 1.0f; + isBold = isItalic = false; + zeromem (lookupTable, sizeof (lookupTable)); + glyphs.clear(); +} - startNewSubPath (line.getPointAlongLine (0, lineThickness)); - lineTo (line.getPointAlongLine (0, -lineThickness)); - lineTo (reversed.getPointAlongLine (0, lineThickness)); - lineTo (reversed.getPointAlongLine (0, -lineThickness)); - closeSubPath(); +void CustomTypeface::setCharacteristics (const String& name_, const float ascent_, const bool isBold_, + const bool isItalic_, const juce_wchar defaultCharacter_) throw() +{ + name = name_; + defaultCharacter = defaultCharacter_; + ascent = ascent_; + isBold = isBold_; + isItalic = isItalic_; } -void Path::addArrow (const Line& line, float lineThickness, - float arrowheadWidth, float arrowheadLength) +void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) throw() { - const Line reversed (line.reversed()); - lineThickness *= 0.5f; - arrowheadWidth *= 0.5f; - arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength()); + // Check that you're not trying to add the same character twice.. + jassert (findGlyph (character, false) == 0); - startNewSubPath (line.getPointAlongLine (0, lineThickness)); - lineTo (line.getPointAlongLine (0, -lineThickness)); - lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); - lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); - lineTo (line.getEnd()); - lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); - lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness)); - closeSubPath(); + if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable)) + lookupTable [character] = (short) glyphs.size(); + + glyphs.add (new GlyphInfo (character, path, width)); } -void Path::addPolygon (const Point& centre, const int numberOfSides, - const float radius, const float startAngle) +void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) throw() { - jassert (numberOfSides > 1); // this would be silly. - - if (numberOfSides > 1) + if (extraAmount != 0) { - const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; + GlyphInfo* const g = findGlyph (char1, true); + jassert (g != 0); // can only add kerning pairs for characters that exist! - for (int i = 0; i < numberOfSides; ++i) - { - const float angle = startAngle + i * angleBetweenPoints; - const Point p (centre.getPointOnCircumference (radius, angle)); + if (g != 0) + g->addKerningPair (char2, extraAmount); + } +} - if (i == 0) - startNewSubPath (p); - else - lineTo (p); - } +CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) throw() +{ + if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable) && lookupTable [character] > 0) + return glyphs [(int) lookupTable [(int) character]]; - closeSubPath(); + for (int i = 0; i < glyphs.size(); ++i) + { + GlyphInfo* const g = glyphs.getUnchecked(i); + if (g->character == character) + return g; } + + if (loadIfNeeded && loadGlyphIfPossible (character)) + return findGlyph (character, false); + + return 0; } -void Path::addStar (const Point& centre, const int numberOfPoints, - const float innerRadius, const float outerRadius, const float startAngle) +CustomTypeface::GlyphInfo* CustomTypeface::findGlyphSubstituting (const juce_wchar character) throw() { - jassert (numberOfPoints > 1); // this would be silly. + GlyphInfo* glyph = findGlyph (character, true); - if (numberOfPoints > 1) + if (glyph == 0) { - const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints; + if (CharacterFunctions::isWhitespace (character) && character != L' ') + glyph = findGlyph (L' ', true); - for (int i = 0; i < numberOfPoints; ++i) + if (glyph == 0) { - const float angle = startAngle + i * angleBetweenPoints; - const Point p (centre.getPointOnCircumference (outerRadius, angle)); - - if (i == 0) - startNewSubPath (p); - else - lineTo (p); + const Font fallbackFont (Font::getFallbackFontName(), 10, 0); + Typeface* const fallbackTypeface = fallbackFont.getTypeface(); + if (fallbackTypeface != 0 && fallbackTypeface != this) + { + //xxx + } - lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f)); + if (glyph == 0) + glyph = findGlyph (defaultCharacter, true); } - - closeSubPath(); } + + return glyph; } -void Path::addBubble (float x, float y, - float w, float h, - float cs, - float tipX, - float tipY, - int whichSide, - float arrowPos, - float arrowWidth) +bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) { - if (w > 1.0f && h > 1.0f) - { - cs = jmin (cs, w * 0.5f, h * 0.5f); - const float cs2 = 2.0f * cs; + return false; +} - startNewSubPath (x + cs, y); +void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) throw() +{ + setCharacteristics (name, typefaceToCopy.getAscent(), isBold, isItalic, defaultCharacter); - if (whichSide == 0) - { - const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; - const float arrowX1 = x + cs + jmax (0.0f, (w - cs2) * arrowPos - halfArrowW); - lineTo (arrowX1, y); - lineTo (tipX, tipY); - lineTo (arrowX1 + halfArrowW * 2.0f, y); - } + for (int i = 0; i < numCharacters; ++i) + { + const juce_wchar c = (juce_wchar) (characterStartIndex + i); - lineTo (x + w - cs, y); + Array glyphIndexes; + Array offsets; + typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets); - if (cs > 0.0f) - addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); + const int glyphIndex = glyphIndexes.getFirst(); - if (whichSide == 3) + if (glyphIndex >= 0 && glyphIndexes.size() > 0) { - const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; - const float arrowY1 = y + cs + jmax (0.0f, (h - cs2) * arrowPos - halfArrowH); - lineTo (x + w, arrowY1); - lineTo (tipX, tipY); - lineTo (x + w, arrowY1 + halfArrowH * 2.0f); - } - - lineTo (x + w, y + h - cs); - - if (cs > 0.0f) - addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); + const float glyphWidth = offsets[1]; - if (whichSide == 2) - { - const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; - const float arrowX1 = x + cs + jmax (0.0f, (w - cs2) * arrowPos - halfArrowW); - lineTo (arrowX1 + halfArrowW * 2.0f, y + h); - lineTo (tipX, tipY); - lineTo (arrowX1, y + h); - } + Path p; + typefaceToCopy.getOutlineForGlyph (glyphIndex, p); - lineTo (x + cs, y + h); + addGlyph (c, p, glyphWidth); - if (cs > 0.0f) - addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); + for (int j = glyphs.size() - 1; --j >= 0;) + { + const juce_wchar char2 = glyphs.getUnchecked (j)->character; + glyphIndexes.clearQuick(); + offsets.clearQuick(); + typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets); - if (whichSide == 1) - { - const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; - const float arrowY1 = y + cs + jmax (0.0f, (h - cs2) * arrowPos - halfArrowH); - lineTo (x, arrowY1 + halfArrowH * 2.0f); - lineTo (tipX, tipY); - lineTo (x, arrowY1); + if (offsets.size() > 1) + addKerningPair (c, char2, offsets[1] - glyphWidth); + } } - - lineTo (x, y + cs); - - if (cs > 0.0f) - addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f - PathHelpers::ellipseAngularIncrement); - - closeSubPath(); } } -void Path::addPath (const Path& other) +bool CustomTypeface::writeToStream (OutputStream& outputStream) { - size_t i = 0; + GZIPCompressorOutputStream out (&outputStream); - while (i < other.numElements) + out.writeString (name); + out.writeBool (isBold); + out.writeBool (isItalic); + out.writeFloat (ascent); + out.writeShort ((short) (unsigned short) defaultCharacter); + out.writeInt (glyphs.size()); + + int i, numKerningPairs = 0; + + for (i = 0; i < glyphs.size(); ++i) { - const float type = other.data.elements [i++]; + const GlyphInfo* const g = glyphs.getUnchecked (i); + out.writeShort ((short) (unsigned short) g->character); + out.writeFloat (g->width); + g->path.writePathToStream (out); - if (type == moveMarker) - { - startNewSubPath (other.data.elements [i], - other.data.elements [i + 1]); + numKerningPairs += g->kerningPairs.size(); + } - i += 2; - } - else if (type == lineMarker) - { - lineTo (other.data.elements [i], - other.data.elements [i + 1]); + out.writeInt (numKerningPairs); - i += 2; - } - else if (type == quadMarker) - { - quadraticTo (other.data.elements [i], - other.data.elements [i + 1], - other.data.elements [i + 2], - other.data.elements [i + 3]); - i += 4; - } - else if (type == cubicMarker) - { - cubicTo (other.data.elements [i], - other.data.elements [i + 1], - other.data.elements [i + 2], - other.data.elements [i + 3], - other.data.elements [i + 4], - other.data.elements [i + 5]); + for (i = 0; i < glyphs.size(); ++i) + { + const GlyphInfo* const g = glyphs.getUnchecked (i); - i += 6; - } - else if (type == closeSubPathMarker) - { - closeSubPath(); - } - else + for (int j = 0; j < g->kerningPairs.size(); ++j) { - // something's gone wrong with the element list! - jassertfalse; + const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j); + out.writeShort ((short) (unsigned short) g->character); + out.writeShort ((short) (unsigned short) p.character2); + out.writeFloat (p.kerningAmount); } } + + return true; } -void Path::addPath (const Path& other, - const AffineTransform& transformToApply) +float CustomTypeface::getAscent() const { - size_t i = 0; - - while (i < other.numElements) - { - const float type = other.data.elements [i++]; - - if (type == closeSubPathMarker) - { - closeSubPath(); - } - else - { - float x = other.data.elements [i++]; - float y = other.data.elements [i++]; - transformToApply.transformPoint (x, y); + return ascent; +} - if (type == moveMarker) - { - startNewSubPath (x, y); - } - else if (type == lineMarker) - { - lineTo (x, y); - } - else if (type == quadMarker) - { - float x2 = other.data.elements [i++]; - float y2 = other.data.elements [i++]; - transformToApply.transformPoint (x2, y2); +float CustomTypeface::getDescent() const +{ + return 1.0f - ascent; +} - quadraticTo (x, y, x2, y2); - } - else if (type == cubicMarker) - { - float x2 = other.data.elements [i++]; - float y2 = other.data.elements [i++]; - float x3 = other.data.elements [i++]; - float y3 = other.data.elements [i++]; - transformToApply.transformPoints (x2, y2, x3, y3); +float CustomTypeface::getStringWidth (const String& text) +{ + float x = 0; + const juce_wchar* t = text; - cubicTo (x, y, x2, y2, x3, y3); - } - else - { - // something's gone wrong with the element list! - jassertfalse; - } - } + while (*t != 0) + { + const GlyphInfo* const glyph = findGlyphSubstituting (*t++); + + if (glyph != 0) + x += glyph->getHorizontalSpacing (*t); } + + return x; } -void Path::applyTransform (const AffineTransform& transform) throw() +void CustomTypeface::getGlyphPositions (const String& text, Array & resultGlyphs, Array& xOffsets) { - size_t i = 0; - pathYMin = pathXMin = 0; - pathYMax = pathXMax = 0; - bool setMaxMin = false; + xOffsets.add (0); + float x = 0; + const juce_wchar* t = text; - while (i < numElements) + while (*t != 0) { - const float type = data.elements [i++]; + const juce_wchar c = *t++; + const GlyphInfo* const glyph = findGlyphSubstituting (c); - if (type == moveMarker) + if (glyph != 0) { - transform.transformPoint (data.elements [i], data.elements [i + 1]); + x += glyph->getHorizontalSpacing (*t); + resultGlyphs.add ((int) glyph->character); + xOffsets.add (x); + } + } +} - if (setMaxMin) - { - pathXMin = jmin (pathXMin, data.elements [i]); - pathXMax = jmax (pathXMax, data.elements [i]); - pathYMin = jmin (pathYMin, data.elements [i + 1]); - pathYMax = jmax (pathYMax, data.elements [i + 1]); - } - else - { - pathXMin = pathXMax = data.elements [i]; - pathYMin = pathYMax = data.elements [i + 1]; - setMaxMin = true; - } +bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) +{ + const GlyphInfo* const glyph = findGlyphSubstituting ((juce_wchar) glyphNumber); + if (glyph != 0) + { + path = glyph->path; + return true; + } - i += 2; - } - else if (type == lineMarker) - { - transform.transformPoint (data.elements [i], data.elements [i + 1]); + return false; +} - pathXMin = jmin (pathXMin, data.elements [i]); - pathXMax = jmax (pathXMax, data.elements [i]); - pathYMin = jmin (pathYMin, data.elements [i + 1]); - pathYMax = jmax (pathYMax, data.elements [i + 1]); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_Typeface.cpp ***/ - i += 2; - } - else if (type == quadMarker) - { - transform.transformPoints (data.elements [i], data.elements [i + 1], - data.elements [i + 2], data.elements [i + 3]); - pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2]); - pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2]); - pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3]); - pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3]); +/*** Start of inlined file: juce_AffineTransform.cpp ***/ +BEGIN_JUCE_NAMESPACE - i += 4; - } - else if (type == cubicMarker) - { - transform.transformPoints (data.elements [i], data.elements [i + 1], - data.elements [i + 2], data.elements [i + 3], - data.elements [i + 4], data.elements [i + 5]); +AffineTransform::AffineTransform() throw() + : mat00 (1.0f), + mat01 (0), + mat02 (0), + mat10 (0), + mat11 (1.0f), + mat12 (0) +{ +} - pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2], data.elements [i + 4]); - pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2], data.elements [i + 4]); - pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); - pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); +AffineTransform::AffineTransform (const AffineTransform& other) throw() + : mat00 (other.mat00), + mat01 (other.mat01), + mat02 (other.mat02), + mat10 (other.mat10), + mat11 (other.mat11), + mat12 (other.mat12) +{ +} - i += 6; - } - } +AffineTransform::AffineTransform (const float mat00_, + const float mat01_, + const float mat02_, + const float mat10_, + const float mat11_, + const float mat12_) throw() + : mat00 (mat00_), + mat01 (mat01_), + mat02 (mat02_), + mat10 (mat10_), + mat11 (mat11_), + mat12 (mat12_) +{ } -const AffineTransform Path::getTransformToScaleToFit (const float x, const float y, - const float w, const float h, - const bool preserveProportions, - const Justification& justification) const +AffineTransform& AffineTransform::operator= (const AffineTransform& other) throw() { - Rectangle bounds (getBounds()); + mat00 = other.mat00; + mat01 = other.mat01; + mat02 = other.mat02; + mat10 = other.mat10; + mat11 = other.mat11; + mat12 = other.mat12; - if (preserveProportions) - { - if (w <= 0 || h <= 0 || bounds.isEmpty()) - return AffineTransform::identity; + return *this; +} - float newW, newH; - const float srcRatio = bounds.getHeight() / bounds.getWidth(); +bool AffineTransform::operator== (const AffineTransform& other) const throw() +{ + return mat00 == other.mat00 + && mat01 == other.mat01 + && mat02 == other.mat02 + && mat10 == other.mat10 + && mat11 == other.mat11 + && mat12 == other.mat12; +} - if (srcRatio > h / w) - { - newW = h / srcRatio; - newH = h; - } - else - { - newW = w; - newH = w * srcRatio; - } +bool AffineTransform::operator!= (const AffineTransform& other) const throw() +{ + return ! operator== (other); +} - float newXCentre = x; - float newYCentre = y; +bool AffineTransform::isIdentity() const throw() +{ + return (mat01 == 0) + && (mat02 == 0) + && (mat10 == 0) + && (mat12 == 0) + && (mat00 == 1.0f) + && (mat11 == 1.0f); +} - 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; +const AffineTransform AffineTransform::identity; - 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; +const AffineTransform AffineTransform::followedBy (const AffineTransform& other) const throw() +{ + return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10, + other.mat00 * mat01 + other.mat01 * mat11, + other.mat00 * mat02 + other.mat01 * mat12 + other.mat02, + other.mat10 * mat00 + other.mat11 * mat10, + other.mat10 * mat01 + other.mat11 * mat11, + other.mat10 * mat02 + other.mat11 * mat12 + other.mat12); +} - return AffineTransform::translation (bounds.getWidth() * -0.5f - bounds.getX(), - bounds.getHeight() * -0.5f - bounds.getY()) - .scaled (newW / bounds.getWidth(), newH / bounds.getHeight()) - .translated (newXCentre, newYCentre); - } - else - { - return AffineTransform::translation (-bounds.getX(), -bounds.getY()) - .scaled (w / bounds.getWidth(), h / bounds.getHeight()) - .translated (x, y); - } +const AffineTransform AffineTransform::followedBy (const float omat00, + const float omat01, + const float omat02, + const float omat10, + const float omat11, + const float omat12) const throw() +{ + return AffineTransform (omat00 * mat00 + omat01 * mat10, + omat00 * mat01 + omat01 * mat11, + omat00 * mat02 + omat01 * mat12 + omat02, + omat10 * mat00 + omat11 * mat10, + omat10 * mat01 + omat11 * mat11, + omat10 * mat02 + omat11 * mat12 + omat12); } -bool Path::contains (const float x, const float y, const float tolerence) const +const AffineTransform AffineTransform::translated (const float dx, + const float dy) const throw() { - if (x <= pathXMin || x >= pathXMax - || y <= pathYMin || y >= pathYMax) - return false; + return AffineTransform (mat00, mat01, mat02 + dx, + mat10, mat11, mat12 + dy); +} - PathFlatteningIterator i (*this, AffineTransform::identity, tolerence); +const AffineTransform AffineTransform::translation (const float dx, + const float dy) throw() +{ + return AffineTransform (1.0f, 0, dx, + 0, 1.0f, dy); +} - int positiveCrossings = 0; - int negativeCrossings = 0; +const AffineTransform AffineTransform::rotated (const float rad) const throw() +{ + const float cosRad = std::cos (rad); + const float sinRad = std::sin (rad); - while (i.next()) - { - if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y)) - { - const float intersectX = i.x1 + (i.x2 - i.x1) * (y - i.y1) / (i.y2 - i.y1); + return followedBy (cosRad, -sinRad, 0, + sinRad, cosRad, 0); +} - if (intersectX <= x) - { - if (i.y1 < i.y2) - ++positiveCrossings; - else - ++negativeCrossings; - } - } - } +const AffineTransform AffineTransform::rotation (const float rad) throw() +{ + const float cosRad = std::cos (rad); + const float sinRad = std::sin (rad); - return useNonZeroWinding ? (negativeCrossings != positiveCrossings) - : ((negativeCrossings + positiveCrossings) & 1) != 0; + return AffineTransform (cosRad, -sinRad, 0, + sinRad, cosRad, 0); } -bool Path::contains (const Point& point, const float tolerence) const +const AffineTransform AffineTransform::rotated (const float angle, + const float pivotX, + const float pivotY) const throw() { - return contains (point.getX(), point.getY(), tolerence); + return translated (-pivotX, -pivotY) + .rotated (angle) + .translated (pivotX, pivotY); } -bool Path::intersectsLine (const Line& line, const float tolerence) +const AffineTransform AffineTransform::rotation (const float angle, + const float pivotX, + const float pivotY) throw() { - PathFlatteningIterator i (*this, AffineTransform::identity, tolerence); - Point intersection; + return translation (-pivotX, -pivotY) + .rotated (angle) + .translated (pivotX, pivotY); +} - while (i.next()) - if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) - return true; +const AffineTransform AffineTransform::scaled (const float factorX, + const float factorY) const throw() +{ + return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02, + factorY * mat10, factorY * mat11, factorY * mat12); +} - return false; +const AffineTransform AffineTransform::scale (const float factorX, + const float factorY) throw() +{ + return AffineTransform (factorX, 0, 0, + 0, factorY, 0); } -const Line Path::getClippedLine (const Line& line, const bool keepSectionOutsidePath) const +const AffineTransform AffineTransform::sheared (const float shearX, + const float shearY) const throw() { - Line result (line); - const bool startInside = contains (line.getStart()); - const bool endInside = contains (line.getEnd()); + return followedBy (1.0f, shearX, 0, + shearY, 1.0f, 0); +} - if (startInside == endInside) +const AffineTransform AffineTransform::inverted() const throw() +{ + double determinant = (mat00 * mat11 - mat10 * mat01); + + if (determinant != 0.0) { - if (keepSectionOutsidePath == startInside) - result = Line(); + determinant = 1.0 / determinant; + + const float dst00 = (float) (mat11 * determinant); + const float dst10 = (float) (-mat10 * determinant); + const float dst01 = (float) (-mat01 * determinant); + const float dst11 = (float) (mat00 * determinant); + + return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01, + dst10, dst11, -mat02 * dst10 - mat12 * dst11); } else { - PathFlatteningIterator i (*this, AffineTransform::identity); - Point intersection; - - while (i.next()) - { - if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) - { - if ((startInside && keepSectionOutsidePath) || (endInside && ! keepSectionOutsidePath)) - result.setStart (intersection); - else - result.setEnd (intersection); - } - } + // singularity.. + return *this; } - - return result; } -float Path::getLength (const AffineTransform& transform) const +bool AffineTransform::isSingularity() const throw() { - float length = 0; - PathFlatteningIterator i (*this, transform); + return (mat00 * mat11 - mat10 * mat01) == 0.0; +} - while (i.next()) - length += Line (i.x1, i.y1, i.x2, i.y2).getLength(); +const AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00, + const float x10, const float y10, + const float x01, const float y01) throw() +{ + return AffineTransform (x10 - x00, x01 - x00, x00, + y10 - y00, y01 - y00, y00); +} - return length; +const AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, + const float sx2, const float sy2, const float tx2, const float ty2, + const float sx3, const float sy3, const float tx3, const float ty3) throw() +{ + return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) + .inverted() + .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); } -const Point Path::getPointAlongPath (float distanceFromStart, const AffineTransform& transform) const +bool AffineTransform::isOnlyTranslation() const throw() { - PathFlatteningIterator i (*this, transform); + return (mat01 == 0) + && (mat10 == 0) + && (mat00 == 1.0f) + && (mat11 == 1.0f); +} - while (i.next()) - { - const Line line (i.x1, i.y1, i.x2, i.y2); - const float lineLength = line.getLength(); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_AffineTransform.cpp ***/ - if (distanceFromStart <= lineLength) - return line.getPointAlongLine (distanceFromStart); - distanceFromStart -= lineLength; - } +/*** Start of inlined file: juce_BorderSize.cpp ***/ +BEGIN_JUCE_NAMESPACE - return Point (i.x2, i.y2); +BorderSize::BorderSize() throw() + : top (0), + left (0), + bottom (0), + right (0) +{ } -float Path::getNearestPoint (const Point& targetPoint, Point& pointOnPath, - const AffineTransform& transform) const +BorderSize::BorderSize (const BorderSize& other) throw() + : top (other.top), + left (other.left), + bottom (other.bottom), + right (other.right) { - PathFlatteningIterator i (*this, transform); - float bestPosition = 0, bestDistance = std::numeric_limits::max(); - float length = 0; - Point pointOnLine; - - while (i.next()) - { - const Line line (i.x1, i.y1, i.x2, i.y2); - const float distance = line.getDistanceFromPoint (targetPoint, pointOnLine); - - if (distance < bestDistance) - { - bestDistance = distance; - bestPosition = length + pointOnLine.getDistanceFrom (line.getStart()); - pointOnPath = pointOnLine; - } +} - length += line.getLength(); - } +BorderSize::BorderSize (const int topGap, + const int leftGap, + const int bottomGap, + const int rightGap) throw() + : top (topGap), + left (leftGap), + bottom (bottomGap), + right (rightGap) +{ +} - return bestPosition; +BorderSize::BorderSize (const int allGaps) throw() + : top (allGaps), + left (allGaps), + bottom (allGaps), + right (allGaps) +{ } -const Path Path::createPathWithRoundedCorners (const float cornerRadius) const +BorderSize::~BorderSize() throw() { - if (cornerRadius <= 0.01f) - return *this; +} - size_t indexOfPathStart = 0, indexOfPathStartThis = 0; - size_t n = 0; - bool lastWasLine = false, firstWasLine = false; - Path p; +void BorderSize::setTop (const int newTopGap) throw() +{ + top = newTopGap; +} - while (n < numElements) - { - const float type = data.elements [n++]; +void BorderSize::setLeft (const int newLeftGap) throw() +{ + left = newLeftGap; +} - if (type == moveMarker) - { - indexOfPathStart = p.numElements; - indexOfPathStartThis = n - 1; - const float x = data.elements [n++]; - const float y = data.elements [n++]; - p.startNewSubPath (x, y); - lastWasLine = false; - firstWasLine = (data.elements [n] == lineMarker); - } - else if (type == lineMarker || type == closeSubPathMarker) - { - float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY; +void BorderSize::setBottom (const int newBottomGap) throw() +{ + bottom = newBottomGap; +} - if (type == lineMarker) - { - endX = data.elements [n++]; - endY = data.elements [n++]; +void BorderSize::setRight (const int newRightGap) throw() +{ + right = newRightGap; +} - if (n > 8) - { - startX = data.elements [n - 8]; - startY = data.elements [n - 7]; - joinX = data.elements [n - 5]; - joinY = data.elements [n - 4]; - } - } - else - { - endX = data.elements [indexOfPathStartThis + 1]; - endY = data.elements [indexOfPathStartThis + 2]; +const Rectangle BorderSize::subtractedFrom (const Rectangle& r) const throw() +{ + return Rectangle (r.getX() + left, + r.getY() + top, + r.getWidth() - (left + right), + r.getHeight() - (top + bottom)); +} - if (n > 6) - { - startX = data.elements [n - 6]; - startY = data.elements [n - 5]; - joinX = data.elements [n - 3]; - joinY = data.elements [n - 2]; - } - } +void BorderSize::subtractFrom (Rectangle& r) const throw() +{ + r.setBounds (r.getX() + left, + r.getY() + top, + r.getWidth() - (left + right), + r.getHeight() - (top + bottom)); +} - if (lastWasLine) - { - const double len1 = juce_hypot (startX - joinX, - startY - joinY); +const Rectangle BorderSize::addedTo (const Rectangle& r) const throw() +{ + return Rectangle (r.getX() - left, + r.getY() - top, + r.getWidth() + (left + right), + r.getHeight() + (top + bottom)); +} - if (len1 > 0) - { - const double propNeeded = jmin (0.5, cornerRadius / len1); +void BorderSize::addTo (Rectangle& r) const throw() +{ + r.setBounds (r.getX() - left, + r.getY() - top, + r.getWidth() + (left + right), + r.getHeight() + (top + bottom)); +} - p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); - p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); - } +bool BorderSize::operator== (const BorderSize& other) const throw() +{ + return top == other.top + && left == other.left + && bottom == other.bottom + && right == other.right; +} - const double len2 = juce_hypot (endX - joinX, - endY - joinY); +bool BorderSize::operator!= (const BorderSize& other) const throw() +{ + return ! operator== (other); +} - if (len2 > 0) - { - const double propNeeded = jmin (0.5, cornerRadius / len2); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_BorderSize.cpp ***/ - p.quadraticTo (joinX, joinY, - (float) (joinX + (endX - joinX) * propNeeded), - (float) (joinY + (endY - joinY) * propNeeded)); - } - p.lineTo (endX, endY); - } - else if (type == lineMarker) - { - p.lineTo (endX, endY); - lastWasLine = true; - } +/*** Start of inlined file: juce_Path.cpp ***/ +BEGIN_JUCE_NAMESPACE - if (type == closeSubPathMarker) - { - if (firstWasLine) - { - startX = data.elements [n - 3]; - startY = data.elements [n - 2]; - joinX = endX; - joinY = endY; - endX = data.elements [indexOfPathStartThis + 4]; - endY = data.elements [indexOfPathStartThis + 5]; +// tests that some co-ords aren't NaNs +#define CHECK_COORDS_ARE_VALID(x, y) \ + jassert (x == x && y == y); - const double len1 = juce_hypot (startX - joinX, - startY - joinY); +namespace PathHelpers +{ + static const float ellipseAngularIncrement = 0.05f; - if (len1 > 0) - { - const double propNeeded = jmin (0.5, cornerRadius / len1); + static const String nextToken (const juce_wchar*& t) + { + while (CharacterFunctions::isWhitespace (*t)) + ++t; - p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); - p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); - } + const juce_wchar* const start = t; - const double len2 = juce_hypot (endX - joinX, - endY - joinY); + while (*t != 0 && ! CharacterFunctions::isWhitespace (*t)) + ++t; - if (len2 > 0) - { - const double propNeeded = jmin (0.5, cornerRadius / len2); + return String (start, (int) (t - start)); + } +} - endX = (float) (joinX + (endX - joinX) * propNeeded); - endY = (float) (joinY + (endY - joinY) * propNeeded); +const float Path::lineMarker = 100001.0f; +const float Path::moveMarker = 100002.0f; +const float Path::quadMarker = 100003.0f; +const float Path::cubicMarker = 100004.0f; +const float Path::closeSubPathMarker = 100005.0f; - p.quadraticTo (joinX, joinY, endX, endY); +Path::Path() + : numElements (0), + pathXMin (0), + pathXMax (0), + pathYMin (0), + pathYMax (0), + useNonZeroWinding (true) +{ +} - p.data.elements [indexOfPathStart + 1] = endX; - p.data.elements [indexOfPathStart + 2] = endY; - } - } +Path::~Path() +{ +} - p.closeSubPath(); - } - } - else if (type == quadMarker) - { - lastWasLine = false; - const float x1 = data.elements [n++]; - const float y1 = data.elements [n++]; - const float x2 = data.elements [n++]; - const float y2 = data.elements [n++]; - p.quadraticTo (x1, y1, x2, y2); - } - else if (type == cubicMarker) - { - lastWasLine = false; - const float x1 = data.elements [n++]; - const float y1 = data.elements [n++]; - const float x2 = data.elements [n++]; - const float y2 = data.elements [n++]; - const float x3 = data.elements [n++]; - const float y3 = data.elements [n++]; - p.cubicTo (x1, y1, x2, y2, x3, y3); - } +Path::Path (const Path& other) + : numElements (other.numElements), + pathXMin (other.pathXMin), + pathXMax (other.pathXMax), + pathYMin (other.pathYMin), + pathYMax (other.pathYMax), + useNonZeroWinding (other.useNonZeroWinding) +{ + if (numElements > 0) + { + data.setAllocatedSize ((int) numElements); + memcpy (data.elements, other.data.elements, numElements * sizeof (float)); } - - return p; } -void Path::loadPathFromStream (InputStream& source) +Path& Path::operator= (const Path& other) { - while (! source.isExhausted()) + if (this != &other) { - switch (source.readByte()) - { - case 'm': - { - const float x = source.readFloat(); - const float y = source.readFloat(); - startNewSubPath (x, y); - break; - } + data.ensureAllocatedSize ((int) other.numElements); - case 'l': - { - const float x = source.readFloat(); - const float y = source.readFloat(); - lineTo (x, y); - break; - } + numElements = other.numElements; + pathXMin = other.pathXMin; + pathXMax = other.pathXMax; + pathYMin = other.pathYMin; + pathYMax = other.pathYMax; + useNonZeroWinding = other.useNonZeroWinding; - case 'q': - { - const float x1 = source.readFloat(); - const float y1 = source.readFloat(); - const float x2 = source.readFloat(); - const float y2 = source.readFloat(); - quadraticTo (x1, y1, x2, y2); - break; - } + if (numElements > 0) + memcpy (data.elements, other.data.elements, numElements * sizeof (float)); + } - case 'b': - { - const float x1 = source.readFloat(); - const float y1 = source.readFloat(); - const float x2 = source.readFloat(); - const float y2 = source.readFloat(); - const float x3 = source.readFloat(); - const float y3 = source.readFloat(); - cubicTo (x1, y1, x2, y2, x3, y3); - break; - } + return *this; +} - case 'c': - closeSubPath(); - break; +bool Path::operator== (const Path& other) const throw() +{ + return ! operator!= (other); +} - case 'n': - useNonZeroWinding = true; - break; +bool Path::operator!= (const Path& other) const throw() +{ + if (numElements != other.numElements || useNonZeroWinding != other.useNonZeroWinding) + return true; - case 'z': - useNonZeroWinding = false; - break; + for (size_t i = 0; i < numElements; ++i) + if (data.elements[i] != other.data.elements[i]) + return true; - case 'e': - return; // end of path marker + return false; +} - default: - jassertfalse; // illegal char in the stream - break; - } - } +void Path::clear() throw() +{ + numElements = 0; + pathXMin = 0; + pathYMin = 0; + pathYMax = 0; + pathXMax = 0; } -void Path::loadPathFromData (const void* const pathData, const int numberOfBytes) +void Path::swapWithPath (Path& other) throw() { - MemoryInputStream in (pathData, numberOfBytes, false); - loadPathFromStream (in); + data.swapWith (other.data); + swapVariables (numElements, other.numElements); + swapVariables (pathXMin, other.pathXMin); + swapVariables (pathXMax, other.pathXMax); + swapVariables (pathYMin, other.pathYMin); + swapVariables (pathYMax, other.pathYMax); + swapVariables (useNonZeroWinding, other.useNonZeroWinding); } -void Path::writePathToStream (OutputStream& dest) const +void Path::setUsingNonZeroWinding (const bool isNonZero) throw() { - dest.writeByte (useNonZeroWinding ? 'n' : 'z'); + useNonZeroWinding = isNonZero; +} + +void Path::scaleToFit (const float x, const float y, const float w, const float h, + const bool preserveProportions) throw() +{ + applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions)); +} +bool Path::isEmpty() const throw() +{ size_t i = 0; + while (i < numElements) { const float type = data.elements [i++]; if (type == moveMarker) { - dest.writeByte ('m'); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - } - else if (type == lineMarker) - { - dest.writeByte ('l'); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - } - else if (type == quadMarker) - { - dest.writeByte ('q'); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - } - else if (type == cubicMarker) - { - dest.writeByte ('b'); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); - dest.writeFloat (data.elements [i++]); + i += 2; } - else if (type == closeSubPathMarker) + else if (type == lineMarker + || type == quadMarker + || type == cubicMarker) { - dest.writeByte ('c'); + return false; } } - dest.writeByte ('e'); // marks the end-of-path + return true; } -const String Path::toString() const +const Rectangle Path::getBounds() const throw() { - MemoryOutputStream s (2048, 2048); - if (! useNonZeroWinding) - s << 'a'; - - size_t i = 0; - float lastMarker = 0.0f; - - while (i < numElements) - { - const float marker = data.elements [i++]; - char markerChar = 0; - int numCoords = 0; - - if (marker == moveMarker) - { - markerChar = 'm'; - numCoords = 2; - } - else if (marker == lineMarker) - { - markerChar = 'l'; - numCoords = 2; - } - else if (marker == quadMarker) - { - markerChar = 'q'; - numCoords = 4; - } - else if (marker == cubicMarker) - { - markerChar = 'c'; - numCoords = 6; - } - else - { - jassert (marker == closeSubPathMarker); - markerChar = 'z'; - } - - if (marker != lastMarker) - { - if (s.getDataSize() != 0) - s << ' '; - - s << markerChar; - lastMarker = marker; - } - - while (--numCoords >= 0 && i < numElements) - { - String coord (data.elements [i++], 3); - - while (coord.endsWithChar ('0') && coord != "0") - coord = coord.dropLastCharacters (1); + return Rectangle (pathXMin, pathYMin, + pathXMax - pathXMin, + pathYMax - pathYMin); +} - if (coord.endsWithChar ('.')) - coord = coord.dropLastCharacters (1); +const Rectangle Path::getBoundsTransformed (const AffineTransform& transform) const throw() +{ + return getBounds().transformed (transform); +} - if (s.getDataSize() != 0) - s << ' '; +void Path::startNewSubPath (const float x, const float y) +{ + CHECK_COORDS_ARE_VALID (x, y); - s << coord; - } + if (numElements == 0) + { + pathXMin = pathXMax = x; + pathYMin = pathYMax = y; + } + else + { + pathXMin = jmin (pathXMin, x); + pathXMax = jmax (pathXMax, x); + pathYMin = jmin (pathYMin, y); + pathYMax = jmax (pathYMax, y); } - return s.toUTF8(); + data.ensureAllocatedSize ((int) numElements + 3); + + data.elements [numElements++] = moveMarker; + data.elements [numElements++] = x; + data.elements [numElements++] = y; } -void Path::restoreFromString (const String& stringVersion) +void Path::startNewSubPath (const Point& start) { - clear(); - setUsingNonZeroWinding (true); - - const juce_wchar* t = stringVersion; - juce_wchar marker = 'm'; - int numValues = 2; - float values [6]; - - for (;;) - { - const String token (PathHelpers::nextToken (t)); - const juce_wchar firstChar = token[0]; - int startNum = 0; + startNewSubPath (start.getX(), start.getY()); +} - if (firstChar == 0) - break; +void Path::lineTo (const float x, const float y) +{ + CHECK_COORDS_ARE_VALID (x, y); - if (firstChar == 'm' || firstChar == 'l') - { - marker = firstChar; - numValues = 2; - } - else if (firstChar == 'q') - { - marker = firstChar; - numValues = 4; - } - else if (firstChar == 'c') - { - marker = firstChar; - numValues = 6; - } - else if (firstChar == 'z') - { - marker = firstChar; - numValues = 0; - } - else if (firstChar == 'a') - { - setUsingNonZeroWinding (false); - continue; - } - else - { - ++startNum; - values [0] = token.getFloatValue(); - } + if (numElements == 0) + startNewSubPath (0, 0); - for (int i = startNum; i < numValues; ++i) - values [i] = PathHelpers::nextToken (t).getFloatValue(); + data.ensureAllocatedSize ((int) numElements + 3); - switch (marker) - { - case 'm': - startNewSubPath (values[0], values[1]); - break; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x; + data.elements [numElements++] = y; - case 'l': - lineTo (values[0], values[1]); - break; + pathXMin = jmin (pathXMin, x); + pathXMax = jmax (pathXMax, x); + pathYMin = jmin (pathYMin, y); + pathYMax = jmax (pathYMax, y); +} - case 'q': - quadraticTo (values[0], values[1], - values[2], values[3]); - break; +void Path::lineTo (const Point& end) +{ + lineTo (end.getX(), end.getY()); +} - case 'c': - cubicTo (values[0], values[1], - values[2], values[3], - values[4], values[5]); - break; +void Path::quadraticTo (const float x1, const float y1, + const float x2, const float y2) +{ + CHECK_COORDS_ARE_VALID (x1, y1); + CHECK_COORDS_ARE_VALID (x2, y2); - case 'z': - closeSubPath(); - break; + if (numElements == 0) + startNewSubPath (0, 0); - default: - jassertfalse; // illegal string format? - break; - } - } -} + data.ensureAllocatedSize ((int) numElements + 5); -Path::Iterator::Iterator (const Path& path_) - : path (path_), - index (0) -{ + data.elements [numElements++] = quadMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + + pathXMin = jmin (pathXMin, x1, x2); + pathXMax = jmax (pathXMax, x1, x2); + pathYMin = jmin (pathYMin, y1, y2); + pathYMax = jmax (pathYMax, y1, y2); } -Path::Iterator::~Iterator() +void Path::quadraticTo (const Point& controlPoint, + const Point& endPoint) { + quadraticTo (controlPoint.getX(), controlPoint.getY(), + endPoint.getX(), endPoint.getY()); } -bool Path::Iterator::next() +void Path::cubicTo (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3) { - const float* const elements = path.data.elements; + CHECK_COORDS_ARE_VALID (x1, y1); + CHECK_COORDS_ARE_VALID (x2, y2); + CHECK_COORDS_ARE_VALID (x3, y3); - if (index < path.numElements) - { - const float type = elements [index++]; + if (numElements == 0) + startNewSubPath (0, 0); - if (type == moveMarker) - { - elementType = startNewSubPath; - x1 = elements [index++]; - y1 = elements [index++]; - } - else if (type == lineMarker) - { - elementType = lineTo; - x1 = elements [index++]; - y1 = elements [index++]; - } - else if (type == quadMarker) - { - elementType = quadraticTo; - x1 = elements [index++]; - y1 = elements [index++]; - x2 = elements [index++]; - y2 = elements [index++]; - } - else if (type == cubicMarker) - { - elementType = cubicTo; - x1 = elements [index++]; - y1 = elements [index++]; - x2 = elements [index++]; - y2 = elements [index++]; - x3 = elements [index++]; - y3 = elements [index++]; - } - else if (type == closeSubPathMarker) - { - elementType = closePath; - } + data.ensureAllocatedSize ((int) numElements + 7); - return true; - } + data.elements [numElements++] = cubicMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + data.elements [numElements++] = x3; + data.elements [numElements++] = y3; - return false; + pathXMin = jmin (pathXMin, x1, x2, x3); + pathXMax = jmax (pathXMax, x1, x2, x3); + pathYMin = jmin (pathYMin, y1, y2, y3); + pathYMax = jmax (pathYMax, y1, y2, y3); } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_Path.cpp ***/ - - -/*** Start of inlined file: juce_PathIterator.cpp ***/ -BEGIN_JUCE_NAMESPACE - -#if JUCE_MSVC && JUCE_DEBUG - #pragma optimize ("t", on) -#endif - -PathFlatteningIterator::PathFlatteningIterator (const Path& path_, - const AffineTransform& transform_, - float tolerence_) - : x2 (0), - y2 (0), - closesSubPath (false), - subPathIndex (-1), - path (path_), - transform (transform_), - points (path_.data.elements), - tolerence (tolerence_ * tolerence_), - subPathCloseX (0), - subPathCloseY (0), - isIdentityTransform (transform_.isIdentity()), - stackBase (32), - index (0), - stackSize (32) +void Path::cubicTo (const Point& controlPoint1, + const Point& controlPoint2, + const Point& endPoint) { - stackPos = stackBase; + cubicTo (controlPoint1.getX(), controlPoint1.getY(), + controlPoint2.getX(), controlPoint2.getY(), + endPoint.getX(), endPoint.getY()); } -PathFlatteningIterator::~PathFlatteningIterator() +void Path::closeSubPath() { + if (numElements > 0 + && data.elements [numElements - 1] != closeSubPathMarker) + { + data.ensureAllocatedSize ((int) numElements + 1); + data.elements [numElements++] = closeSubPathMarker; + } } -bool PathFlatteningIterator::next() +const Point Path::getCurrentPosition() const { - x1 = x2; - y1 = y2; - - float x3 = 0; - float y3 = 0; - float x4 = 0; - float y4 = 0; - float type; + size_t i = numElements - 1; - for (;;) + if (i > 0 && data.elements[i] == closeSubPathMarker) { - if (stackPos == stackBase) + while (i >= 0) { - if (index >= path.numElements) + if (data.elements[i] == moveMarker) { - return false; + i += 2; + break; } - else - { - type = points [index++]; - - if (type != Path::closeSubPathMarker) - { - x2 = points [index++]; - y2 = points [index++]; - if (type == Path::quadMarker) - { - x3 = points [index++]; - y3 = points [index++]; + --i; + } + } - if (! isIdentityTransform) - transform.transformPoints (x2, y2, x3, y3); - } - else if (type == Path::cubicMarker) - { - x3 = points [index++]; - y3 = points [index++]; - x4 = points [index++]; - y4 = points [index++]; + if (i > 0) + return Point (data.elements [i - 1], data.elements [i]); - if (! isIdentityTransform) - transform.transformPoints (x2, y2, x3, y3, x4, y4); - } - else - { - if (! isIdentityTransform) - transform.transformPoint (x2, y2); - } - } - } - } - else - { - type = *--stackPos; + return Point(); +} - if (type != Path::closeSubPathMarker) - { - x2 = *--stackPos; - y2 = *--stackPos; +void Path::addRectangle (const float x, const float y, + const float w, const float h) +{ + float x1 = x, y1 = y, x2 = x + w, y2 = y + h; - if (type == Path::quadMarker) - { - x3 = *--stackPos; - y3 = *--stackPos; - } - else if (type == Path::cubicMarker) - { - x3 = *--stackPos; - y3 = *--stackPos; - x4 = *--stackPos; - y4 = *--stackPos; - } - } - } + if (w < 0) + swapVariables (x1, x2); - if (type == Path::lineMarker) - { - ++subPathIndex; + if (h < 0) + swapVariables (y1, y2); - closesSubPath = (stackPos == stackBase) - && (index < path.numElements) - && (points [index] == Path::closeSubPathMarker) - && x2 == subPathCloseX - && y2 == subPathCloseY; + data.ensureAllocatedSize ((int) numElements + 13); - return true; - } - else if (type == Path::quadMarker) - { - const size_t offset = (size_t) (stackPos - stackBase); + if (numElements == 0) + { + pathXMin = x1; + pathXMax = x2; + pathYMin = y1; + pathYMax = y2; + } + else + { + pathXMin = jmin (pathXMin, x1); + pathXMax = jmax (pathXMax, x2); + pathYMin = jmin (pathYMin, y1); + pathYMax = jmax (pathYMax, y2); + } - if (offset >= stackSize - 10) - { - stackSize <<= 1; - stackBase.realloc (stackSize); - stackPos = stackBase + offset; - } + data.elements [numElements++] = moveMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y2; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x2; + data.elements [numElements++] = y1; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + data.elements [numElements++] = closeSubPathMarker; +} - const float dx1 = x1 - x2; - const float dy1 = y1 - y2; - const float dx2 = x2 - x3; - const float dy2 = y2 - y3; +void Path::addRectangle (const Rectangle& rectangle) +{ + addRectangle ((float) rectangle.getX(), (float) rectangle.getY(), + (float) rectangle.getWidth(), (float) rectangle.getHeight()); +} - const float m1x = (x1 + x2) * 0.5f; - const float m1y = (y1 + y2) * 0.5f; - const float m2x = (x2 + x3) * 0.5f; - const float m2y = (y2 + y3) * 0.5f; - const float m3x = (m1x + m2x) * 0.5f; - const float m3y = (m1y + m2y) * 0.5f; +void Path::addRoundedRectangle (const float x, const float y, + const float w, const float h, + float csx, + float csy) +{ + csx = jmin (csx, w * 0.5f); + csy = jmin (csy, h * 0.5f); + const float cs45x = csx * 0.45f; + const float cs45y = csy * 0.45f; + const float x2 = x + w; + const float y2 = y + h; - if (dx1*dx1 + dy1*dy1 + dx2*dx2 + dy2*dy2 > tolerence) - { - *stackPos++ = y3; - *stackPos++ = x3; - *stackPos++ = m2y; - *stackPos++ = m2x; - *stackPos++ = Path::quadMarker; + startNewSubPath (x + csx, y); + lineTo (x2 - csx, y); + cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); + lineTo (x2, y2 - csy); + cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); + lineTo (x + csx, y2); + cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); + lineTo (x, y + csy); + cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); + closeSubPath(); +} - *stackPos++ = m3y; - *stackPos++ = m3x; - *stackPos++ = m1y; - *stackPos++ = m1x; - *stackPos++ = Path::quadMarker; - } - else - { - *stackPos++ = y3; - *stackPos++ = x3; - *stackPos++ = Path::lineMarker; +void Path::addRoundedRectangle (const float x, const float y, + const float w, const float h, + float cs) +{ + addRoundedRectangle (x, y, w, h, cs, cs); +} - *stackPos++ = m3y; - *stackPos++ = m3x; - *stackPos++ = Path::lineMarker; - } +void Path::addTriangle (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3) +{ + startNewSubPath (x1, y1); + lineTo (x2, y2); + lineTo (x3, y3); + closeSubPath(); +} - jassert (stackPos < stackBase + stackSize); - } - else if (type == Path::cubicMarker) - { - const size_t offset = (size_t) (stackPos - stackBase); +void Path::addQuadrilateral (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4) +{ + startNewSubPath (x1, y1); + lineTo (x2, y2); + lineTo (x3, y3); + lineTo (x4, y4); + closeSubPath(); +} - if (offset >= stackSize - 16) - { - stackSize <<= 1; - stackBase.realloc (stackSize); - stackPos = stackBase + offset; - } +void Path::addEllipse (const float x, const float y, + const float w, const float h) +{ + const float hw = w * 0.5f; + const float hw55 = hw * 0.55f; + const float hh = h * 0.5f; + const float hh45 = hh * 0.55f; + const float cx = x + hw; + const float cy = y + hh; - const float dx1 = x1 - x2; - const float dy1 = y1 - y2; - const float dx2 = x2 - x3; - const float dy2 = y2 - y3; - const float dx3 = x3 - x4; - const float dy3 = y3 - y4; + startNewSubPath (cx, cy - hh); + cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh45, cx + hw, cy); + cubicTo (cx + hw, cy + hh45, cx + hw55, cy + hh, cx, cy + hh); + cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh45, cx - hw, cy); + cubicTo (cx - hw, cy - hh45, cx - hw55, cy - hh, cx, cy - hh); + closeSubPath(); +} - const float m1x = (x1 + x2) * 0.5f; - const float m1y = (y1 + y2) * 0.5f; - const float m2x = (x3 + x2) * 0.5f; - const float m2y = (y3 + y2) * 0.5f; - const float m3x = (x3 + x4) * 0.5f; - const float m3y = (y3 + y4) * 0.5f; - const float m4x = (m1x + m2x) * 0.5f; - const float m4y = (m1y + m2y) * 0.5f; - const float m5x = (m3x + m2x) * 0.5f; - const float m5y = (m3y + m2y) * 0.5f; +void Path::addArc (const float x, const float y, + const float w, const float h, + const float fromRadians, + const float toRadians, + const bool startAsNewSubPath) +{ + const float radiusX = w / 2.0f; + const float radiusY = h / 2.0f; - if (dx1*dx1 + dy1*dy1 + dx2*dx2 - + dy2*dy2 + dx3*dx3 + dy3*dy3 > tolerence) - { - *stackPos++ = y4; - *stackPos++ = x4; - *stackPos++ = m3y; - *stackPos++ = m3x; - *stackPos++ = m5y; - *stackPos++ = m5x; - *stackPos++ = Path::cubicMarker; + addCentredArc (x + radiusX, + y + radiusY, + radiusX, radiusY, + 0.0f, + fromRadians, toRadians, + startAsNewSubPath); +} - *stackPos++ = (m4y + m5y) * 0.5f; - *stackPos++ = (m4x + m5x) * 0.5f; - *stackPos++ = m4y; - *stackPos++ = m4x; - *stackPos++ = m1y; - *stackPos++ = m1x; - *stackPos++ = Path::cubicMarker; - } - else - { - *stackPos++ = y4; - *stackPos++ = x4; - *stackPos++ = Path::lineMarker; +void Path::addCentredArc (const float centreX, const float centreY, + const float radiusX, const float radiusY, + const float rotationOfEllipse, + const float fromRadians, + const float toRadians, + const bool startAsNewSubPath) +{ + if (radiusX > 0.0f && radiusY > 0.0f) + { + const Point centre (centreX, centreY); + const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); + float angle = fromRadians; - *stackPos++ = m5y; - *stackPos++ = m5x; - *stackPos++ = Path::lineMarker; + if (startAsNewSubPath) + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); - *stackPos++ = m4y; - *stackPos++ = m4x; - *stackPos++ = Path::lineMarker; - } - } - else if (type == Path::closeSubPathMarker) + if (fromRadians < toRadians) { - if (x2 != subPathCloseX || y2 != subPathCloseY) - { - x1 = x2; - y1 = y2; - x2 = subPathCloseX; - y2 = subPathCloseY; - closesSubPath = true; + if (startAsNewSubPath) + angle += PathHelpers::ellipseAngularIncrement; - return true; + while (angle < toRadians) + { + lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + angle += PathHelpers::ellipseAngularIncrement; } } else { - jassert (type == Path::moveMarker); + if (startAsNewSubPath) + angle -= PathHelpers::ellipseAngularIncrement; - subPathIndex = -1; - subPathCloseX = x1 = x2; - subPathCloseY = y1 = y2; + while (angle > toRadians) + { + lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + angle -= PathHelpers::ellipseAngularIncrement; + } } + + lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation)); } } -#if JUCE_MSVC && JUCE_DEBUG - #pragma optimize ("", on) // resets optimisations to the project defaults -#endif +void Path::addPieSegment (const float x, const float y, + const float width, const float height, + const float fromRadians, + const float toRadians, + const float innerCircleProportionalSize) +{ + float radiusX = width * 0.5f; + float radiusY = height * 0.5f; + const Point centre (x + radiusX, y + radiusY); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_PathIterator.cpp ***/ + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians)); + addArc (x, y, width, height, fromRadians, toRadians); + + if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f) + { + closeSubPath(); + + if (innerCircleProportionalSize > 0) + { + radiusX *= innerCircleProportionalSize; + radiusY *= innerCircleProportionalSize; + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians)); + addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); + } + } + else + { + if (innerCircleProportionalSize > 0) + { + radiusX *= innerCircleProportionalSize; + radiusY *= innerCircleProportionalSize; -/*** Start of inlined file: juce_PathStrokeType.cpp ***/ -BEGIN_JUCE_NAMESPACE + addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); + } + else + { + lineTo (centre); + } + } -PathStrokeType::PathStrokeType (const float strokeThickness, - const JointStyle jointStyle_, - const EndCapStyle endStyle_) throw() - : thickness (strokeThickness), - jointStyle (jointStyle_), - endStyle (endStyle_) -{ + closeSubPath(); } -PathStrokeType::PathStrokeType (const PathStrokeType& other) throw() - : thickness (other.thickness), - jointStyle (other.jointStyle), - endStyle (other.endStyle) +void Path::addLineSegment (const Line& line, float lineThickness) { -} + const Line reversed (line.reversed()); + lineThickness *= 0.5f; -PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other) throw() -{ - thickness = other.thickness; - jointStyle = other.jointStyle; - endStyle = other.endStyle; - return *this; + startNewSubPath (line.getPointAlongLine (0, lineThickness)); + lineTo (line.getPointAlongLine (0, -lineThickness)); + lineTo (reversed.getPointAlongLine (0, lineThickness)); + lineTo (reversed.getPointAlongLine (0, -lineThickness)); + closeSubPath(); } -PathStrokeType::~PathStrokeType() throw() +void Path::addArrow (const Line& line, float lineThickness, + float arrowheadWidth, float arrowheadLength) { -} + const Line reversed (line.reversed()); + lineThickness *= 0.5f; + arrowheadWidth *= 0.5f; + arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength()); -bool PathStrokeType::operator== (const PathStrokeType& other) const throw() -{ - return thickness == other.thickness - && jointStyle == other.jointStyle - && endStyle == other.endStyle; + startNewSubPath (line.getPointAlongLine (0, lineThickness)); + lineTo (line.getPointAlongLine (0, -lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); + lineTo (line.getEnd()); + lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); + lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness)); + closeSubPath(); } -bool PathStrokeType::operator!= (const PathStrokeType& other) const throw() +void Path::addPolygon (const Point& centre, const int numberOfSides, + const float radius, const float startAngle) { - return ! operator== (other); -} + jassert (numberOfSides > 1); // this would be silly. -namespace PathStrokeHelpers -{ - static bool lineIntersection (const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3, - const float x4, const float y4, - float& intersectionX, - float& intersectionY, - float& distanceBeyondLine1EndSquared) throw() + if (numberOfSides > 1) { - if (x2 != x3 || y2 != y3) - { - const float dx1 = x2 - x1; - const float dy1 = y2 - y1; - const float dx2 = x4 - x3; - const float dy2 = y4 - y3; - const float divisor = dx1 * dy2 - dx2 * dy1; + const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; - if (divisor == 0) - { - if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0))) - { - if (dy1 == 0 && dy2 != 0) - { - const float along = (y1 - y3) / dy2; - intersectionX = x3 + along * dx2; - intersectionY = y1; + for (int i = 0; i < numberOfSides; ++i) + { + const float angle = startAngle + i * angleBetweenPoints; + const Point p (centre.getPointOnCircumference (radius, angle)); - distanceBeyondLine1EndSquared = intersectionX - x2; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if ((x2 > x1) == (intersectionX < x2)) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + if (i == 0) + startNewSubPath (p); + else + lineTo (p); + } - return along >= 0 && along <= 1.0f; - } - else if (dy2 == 0 && dy1 != 0) - { - const float along = (y3 - y1) / dy1; - intersectionX = x1 + along * dx1; - intersectionY = y3; + closeSubPath(); + } +} - distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if (along < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; +void Path::addStar (const Point& centre, const int numberOfPoints, + const float innerRadius, const float outerRadius, const float startAngle) +{ + jassert (numberOfPoints > 1); // this would be silly. - return along >= 0 && along <= 1.0f; - } - else if (dx1 == 0 && dx2 != 0) - { - const float along = (x1 - x3) / dx2; - intersectionX = x1; - intersectionY = y3 + along * dy2; + if (numberOfPoints > 1) + { + const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints; - distanceBeyondLine1EndSquared = intersectionY - y2; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + for (int i = 0; i < numberOfPoints; ++i) + { + const float angle = startAngle + i * angleBetweenPoints; + const Point p (centre.getPointOnCircumference (outerRadius, angle)); - if ((y2 > y1) == (intersectionY < y2)) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + if (i == 0) + startNewSubPath (p); + else + lineTo (p); - return along >= 0 && along <= 1.0f; - } - else if (dx2 == 0 && dx1 != 0) - { - const float along = (x3 - x1) / dx1; - intersectionX = x3; - intersectionY = y1 + along * dy1; + lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f)); + } - distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if (along < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + closeSubPath(); + } +} - return along >= 0 && along <= 1.0f; - } - } +void Path::addBubble (float x, float y, + float w, float h, + float cs, + float tipX, + float tipY, + int whichSide, + float arrowPos, + float arrowWidth) +{ + if (w > 1.0f && h > 1.0f) + { + cs = jmin (cs, w * 0.5f, h * 0.5f); + const float cs2 = 2.0f * cs; - intersectionX = 0.5f * (x2 + x3); - intersectionY = 0.5f * (y2 + y3); + startNewSubPath (x + cs, y); - distanceBeyondLine1EndSquared = 0.0f; - return false; - } - else - { - const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; + if (whichSide == 0) + { + const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; + const float arrowX1 = x + cs + jmax (0.0f, (w - cs2) * arrowPos - halfArrowW); + lineTo (arrowX1, y); + lineTo (tipX, tipY); + lineTo (arrowX1 + halfArrowW * 2.0f, y); + } - intersectionX = x1 + along1 * dx1; - intersectionY = y1 + along1 * dy1; + lineTo (x + w - cs, y); - if (along1 >= 0 && along1 <= 1.0f) - { - const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); + if (cs > 0.0f) + addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); - if (along2 >= 0 && along2 <= divisor) - { - distanceBeyondLine1EndSquared = 0.0f; - return true; - } - } + if (whichSide == 3) + { + const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; + const float arrowY1 = y + cs + jmax (0.0f, (h - cs2) * arrowPos - halfArrowH); + lineTo (x + w, arrowY1); + lineTo (tipX, tipY); + lineTo (x + w, arrowY1 + halfArrowH * 2.0f); + } - distanceBeyondLine1EndSquared = along1 - 1.0f; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); + lineTo (x + w, y + h - cs); - if (along1 < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + if (cs > 0.0f) + addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); - return false; - } + if (whichSide == 2) + { + const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; + const float arrowX1 = x + cs + jmax (0.0f, (w - cs2) * arrowPos - halfArrowW); + lineTo (arrowX1 + halfArrowW * 2.0f, y + h); + lineTo (tipX, tipY); + lineTo (arrowX1, y + h); } - intersectionX = x2; - intersectionY = y2; + lineTo (x + cs, y + h); - distanceBeyondLine1EndSquared = 0.0f; - return true; - } + if (cs > 0.0f) + addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); - static void addEdgeAndJoint (Path& destPath, - const PathStrokeType::JointStyle style, - const float maxMiterExtensionSquared, const float width, - const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3, - const float x4, const float y4, - const float midX, const float midY) - { - if (style == PathStrokeType::beveled - || (x3 == x4 && y3 == y4) - || (x1 == x2 && y1 == y2)) + if (whichSide == 1) { - destPath.lineTo (x2, y2); - destPath.lineTo (x3, y3); + const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; + const float arrowY1 = y + cs + jmax (0.0f, (h - cs2) * arrowPos - halfArrowH); + lineTo (x, arrowY1 + halfArrowH * 2.0f); + lineTo (tipX, tipY); + lineTo (x, arrowY1); } - else - { - float jx, jy, distanceBeyondLine1EndSquared; - - // if they intersect, use this point.. - if (lineIntersection (x1, y1, x2, y2, - x3, y3, x4, y4, - jx, jy, distanceBeyondLine1EndSquared)) - { - destPath.lineTo (jx, jy); - } - else - { - if (style == PathStrokeType::mitered) - { - if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared - && distanceBeyondLine1EndSquared > 0.0f) - { - destPath.lineTo (jx, jy); - } - else - { - // the end sticks out too far, so just use a blunt joint - destPath.lineTo (x2, y2); - destPath.lineTo (x3, y3); - } - } - else - { - // curved joints - float angle1 = std::atan2 (x2 - midX, y2 - midY); - float angle2 = std::atan2 (x3 - midX, y3 - midY); - const float angleIncrement = 0.1f; - - destPath.lineTo (x2, y2); - if (std::abs (angle1 - angle2) > angleIncrement) - { - if (angle2 > angle1 + float_Pi - || (angle2 < angle1 && angle2 >= angle1 - float_Pi)) - { - if (angle2 > angle1) - angle2 -= float_Pi * 2.0f; + lineTo (x, y + cs); - jassert (angle1 <= angle2 + float_Pi); + if (cs > 0.0f) + addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f - PathHelpers::ellipseAngularIncrement); - angle1 -= angleIncrement; - while (angle1 > angle2) - { - destPath.lineTo (midX + width * std::sin (angle1), - midY + width * std::cos (angle1)); + closeSubPath(); + } +} - angle1 -= angleIncrement; - } - } - else - { - if (angle1 > angle2) - angle1 -= float_Pi * 2.0f; +void Path::addPath (const Path& other) +{ + size_t i = 0; - jassert (angle1 >= angle2 - float_Pi); + while (i < other.numElements) + { + const float type = other.data.elements [i++]; - angle1 += angleIncrement; - while (angle1 < angle2) - { - destPath.lineTo (midX + width * std::sin (angle1), - midY + width * std::cos (angle1)); + if (type == moveMarker) + { + startNewSubPath (other.data.elements [i], + other.data.elements [i + 1]); - angle1 += angleIncrement; - } - } - } + i += 2; + } + else if (type == lineMarker) + { + lineTo (other.data.elements [i], + other.data.elements [i + 1]); - destPath.lineTo (x3, y3); - } - } + i += 2; } - } + else if (type == quadMarker) + { + quadraticTo (other.data.elements [i], + other.data.elements [i + 1], + other.data.elements [i + 2], + other.data.elements [i + 3]); + i += 4; + } + else if (type == cubicMarker) + { + cubicTo (other.data.elements [i], + other.data.elements [i + 1], + other.data.elements [i + 2], + other.data.elements [i + 3], + other.data.elements [i + 4], + other.data.elements [i + 5]); - static void addLineEnd (Path& destPath, - const PathStrokeType::EndCapStyle style, - const float x1, const float y1, - const float x2, const float y2, - const float width) - { - if (style == PathStrokeType::butt) + i += 6; + } + else if (type == closeSubPathMarker) { - destPath.lineTo (x2, y2); + closeSubPath(); } else { - float offx1, offy1, offx2, offy2; - - float dx = x2 - x1; - float dy = y2 - y1; - const float len = juce_hypotf (dx, dy); - - if (len == 0) - { - offx1 = offx2 = x1; - offy1 = offy2 = y1; - } - else - { - const float offset = width / len; - dx *= offset; - dy *= offset; - - offx1 = x1 + dy; - offy1 = y1 - dx; - offx2 = x2 + dy; - offy2 = y2 - dx; - } - - if (style == PathStrokeType::square) - { - // sqaure ends - destPath.lineTo (offx1, offy1); - destPath.lineTo (offx2, offy2); - destPath.lineTo (x2, y2); - } - else - { - // rounded ends - const float midx = (offx1 + offx2) * 0.5f; - const float midy = (offy1 + offy2) * 0.5f; - - destPath.cubicTo (x1 + (offx1 - x1) * 0.55f, y1 + (offy1 - y1) * 0.55f, - offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f, - midx, midy); - - destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f, - offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f, - x2, y2); - } + // something's gone wrong with the element list! + jassertfalse; } } +} - struct Arrowhead - { - float startWidth, startLength; - float endWidth, endLength; - }; - - static void addArrowhead (Path& destPath, - const float x1, const float y1, - const float x2, const float y2, - const float tipX, const float tipY, - const float width, - const float arrowheadWidth) - { - Line line (x1, y1, x2, y2); - destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0)); - destPath.lineTo (tipX, tipY); - destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0)); - destPath.lineTo (x2, y2); - } +void Path::addPath (const Path& other, + const AffineTransform& transformToApply) +{ + size_t i = 0; - struct LineSection + while (i < other.numElements) { - float x1, y1, x2, y2; // original line - float lx1, ly1, lx2, ly2; // the left-hand stroke - float rx1, ry1, rx2, ry2; // the right-hand stroke - }; + const float type = other.data.elements [i++]; - static void shortenSubPath (Array& subPath, float amountAtStart, float amountAtEnd) - { - while (amountAtEnd > 0 && subPath.size() > 0) + if (type == closeSubPathMarker) { - LineSection& l = subPath.getReference (subPath.size() - 1); - float dx = l.rx2 - l.rx1; - float dy = l.ry2 - l.ry1; - const float len = juce_hypotf (dx, dy); + closeSubPath(); + } + else + { + float x = other.data.elements [i++]; + float y = other.data.elements [i++]; + transformToApply.transformPoint (x, y); - if (len <= amountAtEnd && subPath.size() > 1) + if (type == moveMarker) { - LineSection& prev = subPath.getReference (subPath.size() - 2); - prev.x2 = l.x2; - prev.y2 = l.y2; - subPath.removeLast(); - amountAtEnd -= len; + startNewSubPath (x, y); } - else + else if (type == lineMarker) { - const float prop = jmin (0.9999f, amountAtEnd / len); - dx *= prop; - dy *= prop; - l.rx1 += dx; - l.ry1 += dy; - l.lx2 += dx; - l.ly2 += dy; - break; + lineTo (x, y); } - } + else if (type == quadMarker) + { + float x2 = other.data.elements [i++]; + float y2 = other.data.elements [i++]; + transformToApply.transformPoint (x2, y2); - while (amountAtStart > 0 && subPath.size() > 0) - { - LineSection& l = subPath.getReference (0); - float dx = l.rx2 - l.rx1; - float dy = l.ry2 - l.ry1; - const float len = juce_hypotf (dx, dy); + quadraticTo (x, y, x2, y2); + } + else if (type == cubicMarker) + { + float x2 = other.data.elements [i++]; + float y2 = other.data.elements [i++]; + float x3 = other.data.elements [i++]; + float y3 = other.data.elements [i++]; + transformToApply.transformPoints (x2, y2, x3, y3); - if (len <= amountAtStart && subPath.size() > 1) - { - LineSection& next = subPath.getReference (1); - next.x1 = l.x1; - next.y1 = l.y1; - subPath.remove (0); - amountAtStart -= len; + cubicTo (x, y, x2, y2, x3, y3); } else { - const float prop = jmin (0.9999f, amountAtStart / len); - dx *= prop; - dy *= prop; - l.rx2 -= dx; - l.ry2 -= dy; - l.lx1 -= dx; - l.ly1 -= dy; - break; + // something's gone wrong with the element list! + jassertfalse; } } } +} - static void addSubPath (Path& destPath, Array& subPath, - const bool isClosed, const float width, const float maxMiterExtensionSquared, - const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle, - const Arrowhead* const arrowhead) - { - jassert (subPath.size() > 0); - - if (arrowhead != 0) - shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength); - - const LineSection& firstLine = subPath.getReference (0); +void Path::applyTransform (const AffineTransform& transform) throw() +{ + size_t i = 0; + pathYMin = pathXMin = 0; + pathYMax = pathXMax = 0; + bool setMaxMin = false; - float lastX1 = firstLine.lx1; - float lastY1 = firstLine.ly1; - float lastX2 = firstLine.lx2; - float lastY2 = firstLine.ly2; + while (i < numElements) + { + const float type = data.elements [i++]; - if (isClosed) - { - destPath.startNewSubPath (lastX1, lastY1); - } - else + if (type == moveMarker) { - destPath.startNewSubPath (firstLine.rx2, firstLine.ry2); + transform.transformPoint (data.elements [i], data.elements [i + 1]); - if (arrowhead != 0) - addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1, - width, arrowhead->startWidth); + if (setMaxMin) + { + pathXMin = jmin (pathXMin, data.elements [i]); + pathXMax = jmax (pathXMax, data.elements [i]); + pathYMin = jmin (pathYMin, data.elements [i + 1]); + pathYMax = jmax (pathYMax, data.elements [i + 1]); + } else - addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width); - } + { + pathXMin = pathXMax = data.elements [i]; + pathYMin = pathYMax = data.elements [i + 1]; + setMaxMin = true; + } - int i; - for (i = 1; i < subPath.size(); ++i) + i += 2; + } + else if (type == lineMarker) { - const LineSection& l = subPath.getReference (i); + transform.transformPoint (data.elements [i], data.elements [i + 1]); - addEdgeAndJoint (destPath, jointStyle, - maxMiterExtensionSquared, width, - lastX1, lastY1, lastX2, lastY2, - l.lx1, l.ly1, l.lx2, l.ly2, - l.x1, l.y1); + pathXMin = jmin (pathXMin, data.elements [i]); + pathXMax = jmax (pathXMax, data.elements [i]); + pathYMin = jmin (pathYMin, data.elements [i + 1]); + pathYMax = jmax (pathYMax, data.elements [i + 1]); - lastX1 = l.lx1; - lastY1 = l.ly1; - lastX2 = l.lx2; - lastY2 = l.ly2; + i += 2; } - - const LineSection& lastLine = subPath.getReference (subPath.size() - 1); - - if (isClosed) + else if (type == quadMarker) { - const LineSection& l = subPath.getReference (0); + transform.transformPoints (data.elements [i], data.elements [i + 1], + data.elements [i + 2], data.elements [i + 3]); - addEdgeAndJoint (destPath, jointStyle, - maxMiterExtensionSquared, width, - lastX1, lastY1, lastX2, lastY2, - l.lx1, l.ly1, l.lx2, l.ly2, - l.x1, l.y1); + pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2]); + pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2]); + pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3]); + pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3]); - destPath.closeSubPath(); - destPath.startNewSubPath (lastLine.rx1, lastLine.ry1); + i += 4; } - else + else if (type == cubicMarker) { - destPath.lineTo (lastX2, lastY2); + transform.transformPoints (data.elements [i], data.elements [i + 1], + data.elements [i + 2], data.elements [i + 3], + data.elements [i + 4], data.elements [i + 5]); - if (arrowhead != 0) - addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2, - width, arrowhead->endWidth); - else - addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width); - } + pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2], data.elements [i + 4]); + pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2], data.elements [i + 4]); + pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); + pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); - lastX1 = lastLine.rx1; - lastY1 = lastLine.ry1; - lastX2 = lastLine.rx2; - lastY2 = lastLine.ry2; + i += 6; + } + } +} - for (i = subPath.size() - 1; --i >= 0;) - { - const LineSection& l = subPath.getReference (i); +const AffineTransform Path::getTransformToScaleToFit (const float x, const float y, + const float w, const float h, + const bool preserveProportions, + const Justification& justification) const +{ + Rectangle bounds (getBounds()); - addEdgeAndJoint (destPath, jointStyle, - maxMiterExtensionSquared, width, - lastX1, lastY1, lastX2, lastY2, - l.rx1, l.ry1, l.rx2, l.ry2, - l.x2, l.y2); + if (preserveProportions) + { + if (w <= 0 || h <= 0 || bounds.isEmpty()) + return AffineTransform::identity; - lastX1 = l.rx1; - lastY1 = l.ry1; - lastX2 = l.rx2; - lastY2 = l.ry2; - } + float newW, newH; + const float srcRatio = bounds.getHeight() / bounds.getWidth(); - if (isClosed) + if (srcRatio > h / w) { - addEdgeAndJoint (destPath, jointStyle, - maxMiterExtensionSquared, width, - lastX1, lastY1, lastX2, lastY2, - lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2, - lastLine.x2, lastLine.y2); + newW = h / srcRatio; + newH = h; } else { - // do the last line - destPath.lineTo (lastX2, lastY2); - } - - destPath.closeSubPath(); - } - - static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle, - const PathStrokeType::EndCapStyle endStyle, - Path& destPath, const Path& source, - const AffineTransform& transform, - const float extraAccuracy, const Arrowhead* const arrowhead) - { - if (thickness <= 0) - { - destPath.clear(); - return; + newW = w; + newH = w * srcRatio; } - const Path* sourcePath = &source; - Path temp; + float newXCentre = x; + float newYCentre = y; - if (sourcePath == &destPath) - { - destPath.swapWithPath (temp); - sourcePath = &temp; - } + if (justification.testFlags (Justification::left)) + newXCentre += newW * 0.5f; + else if (justification.testFlags (Justification::right)) + newXCentre += w - newW * 0.5f; else - { - destPath.clear(); - } + newXCentre += w * 0.5f; - destPath.setUsingNonZeroWinding (true); + 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; - const float maxMiterExtensionSquared = 9.0f * thickness * thickness; - const float width = 0.5f * thickness; + return AffineTransform::translation (bounds.getWidth() * -0.5f - bounds.getX(), + bounds.getHeight() * -0.5f - bounds.getY()) + .scaled (newW / bounds.getWidth(), newH / bounds.getHeight()) + .translated (newXCentre, newYCentre); + } + else + { + return AffineTransform::translation (-bounds.getX(), -bounds.getY()) + .scaled (w / bounds.getWidth(), h / bounds.getHeight()) + .translated (x, y); + } +} - // Iterate the path, creating a list of the - // left/right-hand lines along either side of it... - PathFlatteningIterator it (*sourcePath, transform, 9.0f / extraAccuracy); +bool Path::contains (const float x, const float y, const float tolerence) const +{ + if (x <= pathXMin || x >= pathXMax + || y <= pathYMin || y >= pathYMax) + return false; - Array subPath; - subPath.ensureStorageAllocated (512); - LineSection l; - l.x1 = 0; - l.y1 = 0; + PathFlatteningIterator i (*this, AffineTransform::identity, tolerence); - const float minSegmentLength = 2.0f / (extraAccuracy * extraAccuracy); + int positiveCrossings = 0; + int negativeCrossings = 0; - while (it.next()) + while (i.next()) + { + if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y)) { - if (it.subPathIndex == 0) - { - if (subPath.size() > 0) - { - addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); - subPath.clearQuick(); - } + const float intersectX = i.x1 + (i.x2 - i.x1) * (y - i.y1) / (i.y2 - i.y1); - l.x1 = it.x1; - l.y1 = it.y1; + if (intersectX <= x) + { + if (i.y1 < i.y2) + ++positiveCrossings; + else + ++negativeCrossings; } + } + } - l.x2 = it.x2; - l.y2 = it.y2; - - float dx = l.x2 - l.x1; - float dy = l.y2 - l.y1; + return useNonZeroWinding ? (negativeCrossings != positiveCrossings) + : ((negativeCrossings + positiveCrossings) & 1) != 0; +} - const float hypotSquared = dx*dx + dy*dy; +bool Path::contains (const Point& point, const float tolerence) const +{ + return contains (point.getX(), point.getY(), tolerence); +} - if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath()) - { - const float len = std::sqrt (hypotSquared); +bool Path::intersectsLine (const Line& line, const float tolerence) +{ + PathFlatteningIterator i (*this, AffineTransform::identity, tolerence); + Point intersection; - if (len == 0) - { - l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; - l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; - } - else - { - const float offset = width / len; - dx *= offset; - dy *= offset; + while (i.next()) + if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) + return true; - l.rx2 = l.x1 - dy; - l.ry2 = l.y1 + dx; - l.lx1 = l.x1 + dy; - l.ly1 = l.y1 - dx; + return false; +} - l.lx2 = l.x2 + dy; - l.ly2 = l.y2 - dx; - l.rx1 = l.x2 - dy; - l.ry1 = l.y2 + dx; - } +const Line Path::getClippedLine (const Line& line, const bool keepSectionOutsidePath) const +{ + Line result (line); + const bool startInside = contains (line.getStart()); + const bool endInside = contains (line.getEnd()); - subPath.add (l); + if (startInside == endInside) + { + if (keepSectionOutsidePath == startInside) + result = Line(); + } + else + { + PathFlatteningIterator i (*this, AffineTransform::identity); + Point intersection; - if (it.closesSubPath) - { - addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); - subPath.clearQuick(); - } + while (i.next()) + { + if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) + { + if ((startInside && keepSectionOutsidePath) || (endInside && ! keepSectionOutsidePath)) + result.setStart (intersection); else - { - l.x1 = it.x2; - l.y1 = it.y2; - } + result.setEnd (intersection); } } - - if (subPath.size() > 0) - addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); } + + return result; } -void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath, - const AffineTransform& transform, const float extraAccuracy) const +float Path::getLength (const AffineTransform& transform) const { - PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath, - transform, extraAccuracy, 0); + float length = 0; + PathFlatteningIterator i (*this, transform); + + while (i.next()) + length += Line (i.x1, i.y1, i.x2, i.y2).getLength(); + + return length; } -void PathStrokeType::createDashedStroke (Path& destPath, - const Path& sourcePath, - const float* dashLengths, - int numDashLengths, - const AffineTransform& transform, - const float extraAccuracy) const +const Point Path::getPointAlongPath (float distanceFromStart, const AffineTransform& transform) const { - if (thickness <= 0) - return; + PathFlatteningIterator i (*this, transform); - // this should really be an even number.. - jassert ((numDashLengths & 1) == 0); + while (i.next()) + { + const Line line (i.x1, i.y1, i.x2, i.y2); + const float lineLength = line.getLength(); - Path newDestPath; - PathFlatteningIterator it (sourcePath, transform, 9.0f / extraAccuracy); + if (distanceFromStart <= lineLength) + return line.getPointAlongLine (distanceFromStart); - bool first = true; - int dashNum = 0; - float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f; - float dx = 0.0f, dy = 0.0f; + distanceFromStart -= lineLength; + } - for (;;) + return Point (i.x2, i.y2); +} + +float Path::getNearestPoint (const Point& targetPoint, Point& pointOnPath, + const AffineTransform& transform) const +{ + PathFlatteningIterator i (*this, transform); + float bestPosition = 0, bestDistance = std::numeric_limits::max(); + float length = 0; + Point pointOnLine; + + while (i.next()) { - const bool isSolid = ((dashNum & 1) == 0); - const float dashLen = dashLengths [dashNum++ % numDashLengths]; + const Line line (i.x1, i.y1, i.x2, i.y2); + const float distance = line.getDistanceFromPoint (targetPoint, pointOnLine); - jassert (dashLen > 0); // must be a positive increment! - if (dashLen <= 0) - break; + if (distance < bestDistance) + { + bestDistance = distance; + bestPosition = length + pointOnLine.getDistanceFrom (line.getStart()); + pointOnPath = pointOnLine; + } - pos += dashLen; + length += line.getLength(); + } - while (pos > lineEndPos) + return bestPosition; +} + +const Path Path::createPathWithRoundedCorners (const float cornerRadius) const +{ + if (cornerRadius <= 0.01f) + return *this; + + size_t indexOfPathStart = 0, indexOfPathStartThis = 0; + size_t n = 0; + bool lastWasLine = false, firstWasLine = false; + Path p; + + while (n < numElements) + { + const float type = data.elements [n++]; + + if (type == moveMarker) { - if (! it.next()) + indexOfPathStart = p.numElements; + indexOfPathStartThis = n - 1; + const float x = data.elements [n++]; + const float y = data.elements [n++]; + p.startNewSubPath (x, y); + lastWasLine = false; + firstWasLine = (data.elements [n] == lineMarker); + } + else if (type == lineMarker || type == closeSubPathMarker) + { + float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY; + + if (type == lineMarker) { - if (isSolid && ! first) - newDestPath.lineTo (it.x2, it.y2); + endX = data.elements [n++]; + endY = data.elements [n++]; - createStrokedPath (destPath, newDestPath, AffineTransform::identity, extraAccuracy); - return; + if (n > 8) + { + startX = data.elements [n - 8]; + startY = data.elements [n - 7]; + joinX = data.elements [n - 5]; + joinY = data.elements [n - 4]; + } } - - if (isSolid && ! first) - newDestPath.lineTo (it.x1, it.y1); else - newDestPath.startNewSubPath (it.x1, it.y1); - - dx = it.x2 - it.x1; - dy = it.y2 - it.y1; - lineLen = juce_hypotf (dx, dy); - lineEndPos += lineLen; - first = it.closesSubPath; - } + { + endX = data.elements [indexOfPathStartThis + 1]; + endY = data.elements [indexOfPathStartThis + 2]; - const float alpha = (pos - (lineEndPos - lineLen)) / lineLen; + if (n > 6) + { + startX = data.elements [n - 6]; + startY = data.elements [n - 5]; + joinX = data.elements [n - 3]; + joinY = data.elements [n - 2]; + } + } - if (isSolid) - newDestPath.lineTo (it.x1 + dx * alpha, - it.y1 + dy * alpha); - else - newDestPath.startNewSubPath (it.x1 + dx * alpha, - it.y1 + dy * alpha); - } -} + if (lastWasLine) + { + const double len1 = juce_hypot (startX - joinX, + startY - joinY); -void PathStrokeType::createStrokeWithArrowheads (Path& destPath, - const Path& sourcePath, - const float arrowheadStartWidth, const float arrowheadStartLength, - const float arrowheadEndWidth, const float arrowheadEndLength, - const AffineTransform& transform, - const float extraAccuracy) const -{ - PathStrokeHelpers::Arrowhead head; - head.startWidth = arrowheadStartWidth; - head.startLength = arrowheadStartLength; - head.endWidth = arrowheadEndWidth; - head.endLength = arrowheadEndLength; + if (len1 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len1); - PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, - destPath, sourcePath, transform, extraAccuracy, &head); -} + p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); + p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); + } -END_JUCE_NAMESPACE -/*** End of inlined file: juce_PathStrokeType.cpp ***/ + const double len2 = juce_hypot (endX - joinX, + endY - joinY); + if (len2 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len2); -/*** Start of inlined file: juce_PositionedRectangle.cpp ***/ -BEGIN_JUCE_NAMESPACE + p.quadraticTo (joinX, joinY, + (float) (joinX + (endX - joinX) * propNeeded), + (float) (joinY + (endY - joinY) * propNeeded)); + } -PositionedRectangle::PositionedRectangle() throw() - : x (0.0), - y (0.0), - w (0.0), - h (0.0), - xMode (anchorAtLeftOrTop | absoluteFromParentTopLeft), - yMode (anchorAtLeftOrTop | absoluteFromParentTopLeft), - wMode (absoluteSize), - hMode (absoluteSize) -{ -} + p.lineTo (endX, endY); + } + else if (type == lineMarker) + { + p.lineTo (endX, endY); + lastWasLine = true; + } -PositionedRectangle::PositionedRectangle (const PositionedRectangle& other) throw() - : x (other.x), - y (other.y), - w (other.w), - h (other.h), - xMode (other.xMode), - yMode (other.yMode), - wMode (other.wMode), - hMode (other.hMode) -{ -} + if (type == closeSubPathMarker) + { + if (firstWasLine) + { + startX = data.elements [n - 3]; + startY = data.elements [n - 2]; + joinX = endX; + joinY = endY; + endX = data.elements [indexOfPathStartThis + 4]; + endY = data.elements [indexOfPathStartThis + 5]; -PositionedRectangle& PositionedRectangle::operator= (const PositionedRectangle& other) throw() -{ - x = other.x; - y = other.y; - w = other.w; - h = other.h; - xMode = other.xMode; - yMode = other.yMode; - wMode = other.wMode; - hMode = other.hMode; + const double len1 = juce_hypot (startX - joinX, + startY - joinY); - return *this; -} + if (len1 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len1); -PositionedRectangle::~PositionedRectangle() throw() -{ -} + p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); + p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); + } -bool PositionedRectangle::operator== (const PositionedRectangle& other) const throw() -{ - return x == other.x - && y == other.y - && w == other.w - && h == other.h - && xMode == other.xMode - && yMode == other.yMode - && wMode == other.wMode - && hMode == other.hMode; -} + const double len2 = juce_hypot (endX - joinX, + endY - joinY); -bool PositionedRectangle::operator!= (const PositionedRectangle& other) const throw() -{ - return ! operator== (other); -} + if (len2 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len2); -PositionedRectangle::PositionedRectangle (const String& stringVersion) throw() -{ - StringArray tokens; - tokens.addTokens (stringVersion, false); + endX = (float) (joinX + (endX - joinX) * propNeeded); + endY = (float) (joinY + (endY - joinY) * propNeeded); - decodePosString (tokens [0], xMode, x); - decodePosString (tokens [1], yMode, y); - decodeSizeString (tokens [2], wMode, w); - decodeSizeString (tokens [3], hMode, h); -} + p.quadraticTo (joinX, joinY, endX, endY); -const String PositionedRectangle::toString() const throw() -{ - String s; - s.preallocateStorage (12); + p.data.elements [indexOfPathStart + 1] = endX; + p.data.elements [indexOfPathStart + 2] = endY; + } + } - addPosDescription (s, xMode, x); - s << ' '; - addPosDescription (s, yMode, y); - s << ' '; - addSizeDescription (s, wMode, w); - s << ' '; - addSizeDescription (s, hMode, h); + p.closeSubPath(); + } + } + else if (type == quadMarker) + { + lastWasLine = false; + const float x1 = data.elements [n++]; + const float y1 = data.elements [n++]; + const float x2 = data.elements [n++]; + const float y2 = data.elements [n++]; + p.quadraticTo (x1, y1, x2, y2); + } + else if (type == cubicMarker) + { + lastWasLine = false; + const float x1 = data.elements [n++]; + const float y1 = data.elements [n++]; + const float x2 = data.elements [n++]; + const float y2 = data.elements [n++]; + const float x3 = data.elements [n++]; + const float y3 = data.elements [n++]; + p.cubicTo (x1, y1, x2, y2, x3, y3); + } + } - return s; + return p; } -const Rectangle PositionedRectangle::getRectangle (const Rectangle& target) const throw() +void Path::loadPathFromStream (InputStream& source) { - jassert (! target.isEmpty()); + while (! source.isExhausted()) + { + switch (source.readByte()) + { + case 'm': + { + const float x = source.readFloat(); + const float y = source.readFloat(); + startNewSubPath (x, y); + break; + } - double x_, y_, w_, h_; - applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth()); - applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight()); + case 'l': + { + const float x = source.readFloat(); + const float y = source.readFloat(); + lineTo (x, y); + break; + } - return Rectangle (roundToInt (x_), roundToInt (y_), - roundToInt (w_), roundToInt (h_)); -} + case 'q': + { + const float x1 = source.readFloat(); + const float y1 = source.readFloat(); + const float x2 = source.readFloat(); + const float y2 = source.readFloat(); + quadraticTo (x1, y1, x2, y2); + break; + } -void PositionedRectangle::getRectangleDouble (const Rectangle& target, - double& x_, double& y_, - double& w_, double& h_) const throw() -{ - jassert (! target.isEmpty()); + case 'b': + { + const float x1 = source.readFloat(); + const float y1 = source.readFloat(); + const float x2 = source.readFloat(); + const float y2 = source.readFloat(); + const float x3 = source.readFloat(); + const float y3 = source.readFloat(); + cubicTo (x1, y1, x2, y2, x3, y3); + break; + } - applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth()); - applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight()); -} + case 'c': + closeSubPath(); + break; -void PositionedRectangle::applyToComponent (Component& comp) const throw() -{ - comp.setBounds (getRectangle (Rectangle (comp.getParentWidth(), comp.getParentHeight()))); -} + case 'n': + useNonZeroWinding = true; + break; -void PositionedRectangle::updateFrom (const Rectangle& rectangle, - const Rectangle& target) throw() -{ - updatePosAndSize (x, w, rectangle.getX(), rectangle.getWidth(), xMode, wMode, target.getX(), target.getWidth()); - updatePosAndSize (y, h, rectangle.getY(), rectangle.getHeight(), yMode, hMode, target.getY(), target.getHeight()); -} + case 'z': + useNonZeroWinding = false; + break; -void PositionedRectangle::updateFromDouble (const double newX, const double newY, - const double newW, const double newH, - const Rectangle& target) throw() -{ - updatePosAndSize (x, w, newX, newW, xMode, wMode, target.getX(), target.getWidth()); - updatePosAndSize (y, h, newY, newH, yMode, hMode, target.getY(), target.getHeight()); -} + case 'e': + return; // end of path marker -void PositionedRectangle::updateFromComponent (const Component& comp) throw() -{ - if (comp.getParentComponent() == 0 && ! comp.isOnDesktop()) - updateFrom (comp.getBounds(), Rectangle()); - else - updateFrom (comp.getBounds(), Rectangle (comp.getParentWidth(), comp.getParentHeight())); + default: + jassertfalse; // illegal char in the stream + break; + } + } } -PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointX() const throw() +void Path::loadPathFromData (const void* const pathData, const int numberOfBytes) { - return (AnchorPoint) (xMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre)); + MemoryInputStream in (pathData, numberOfBytes, false); + loadPathFromStream (in); } -PositionedRectangle::PositionMode PositionedRectangle::getPositionModeX() const throw() +void Path::writePathToStream (OutputStream& dest) const { - return (PositionMode) (xMode & (absoluteFromParentTopLeft - | absoluteFromParentBottomRight - | absoluteFromParentCentre - | proportionOfParentSize)); -} + dest.writeByte (useNonZeroWinding ? 'n' : 'z'); -PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointY() const throw() -{ - return (AnchorPoint) (yMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre)); -} + size_t i = 0; + while (i < numElements) + { + const float type = data.elements [i++]; -PositionedRectangle::PositionMode PositionedRectangle::getPositionModeY() const throw() -{ - return (PositionMode) (yMode & (absoluteFromParentTopLeft - | absoluteFromParentBottomRight - | absoluteFromParentCentre - | proportionOfParentSize)); -} + if (type == moveMarker) + { + dest.writeByte ('m'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == lineMarker) + { + dest.writeByte ('l'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == quadMarker) + { + dest.writeByte ('q'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == cubicMarker) + { + dest.writeByte ('b'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == closeSubPathMarker) + { + dest.writeByte ('c'); + } + } -PositionedRectangle::SizeMode PositionedRectangle::getWidthMode() const throw() -{ - return (SizeMode) wMode; + dest.writeByte ('e'); // marks the end-of-path } -PositionedRectangle::SizeMode PositionedRectangle::getHeightMode() const throw() +const String Path::toString() const { - return (SizeMode) hMode; -} + MemoryOutputStream s (2048, 2048); + if (! useNonZeroWinding) + s << 'a'; -void PositionedRectangle::setModes (const AnchorPoint xAnchor, - const PositionMode xMode_, - const AnchorPoint yAnchor, - const PositionMode yMode_, - const SizeMode widthMode, - const SizeMode heightMode, - const Rectangle& target) throw() -{ - if (xMode != (xAnchor | xMode_) || wMode != widthMode) + size_t i = 0; + float lastMarker = 0.0f; + + while (i < numElements) { - double tx, tw; - applyPosAndSize (tx, tw, x, w, xMode, wMode, target.getX(), target.getWidth()); + const float marker = data.elements [i++]; + char markerChar = 0; + int numCoords = 0; - xMode = (uint8) (xAnchor | xMode_); - wMode = (uint8) widthMode; + if (marker == moveMarker) + { + markerChar = 'm'; + numCoords = 2; + } + else if (marker == lineMarker) + { + markerChar = 'l'; + numCoords = 2; + } + else if (marker == quadMarker) + { + markerChar = 'q'; + numCoords = 4; + } + else if (marker == cubicMarker) + { + markerChar = 'c'; + numCoords = 6; + } + else + { + jassert (marker == closeSubPathMarker); + markerChar = 'z'; + } - updatePosAndSize (x, w, tx, tw, xMode, wMode, target.getX(), target.getWidth()); - } + if (marker != lastMarker) + { + if (s.getDataSize() != 0) + s << ' '; - if (yMode != (yAnchor | yMode_) || hMode != heightMode) - { - double ty, th; - applyPosAndSize (ty, th, y, h, yMode, hMode, target.getY(), target.getHeight()); + s << markerChar; + lastMarker = marker; + } - yMode = (uint8) (yAnchor | yMode_); - hMode = (uint8) heightMode; + while (--numCoords >= 0 && i < numElements) + { + String coord (data.elements [i++], 3); - updatePosAndSize (y, h, ty, th, yMode, hMode, target.getY(), target.getHeight()); - } -} + while (coord.endsWithChar ('0') && coord != "0") + coord = coord.dropLastCharacters (1); -bool PositionedRectangle::isPositionAbsolute() const throw() -{ - return xMode == absoluteFromParentTopLeft - && yMode == absoluteFromParentTopLeft - && wMode == absoluteSize - && hMode == absoluteSize; -} + if (coord.endsWithChar ('.')) + coord = coord.dropLastCharacters (1); -void PositionedRectangle::addPosDescription (String& s, const uint8 mode, const double value) const throw() -{ - if ((mode & proportionOfParentSize) != 0) - { - s << (roundToInt (value * 100000.0) / 1000.0) << '%'; - } - else - { - s << (roundToInt (value * 100.0) / 100.0); + if (s.getDataSize() != 0) + s << ' '; - if ((mode & absoluteFromParentBottomRight) != 0) - s << 'R'; - else if ((mode & absoluteFromParentCentre) != 0) - s << 'C'; + s << coord; + } } - if ((mode & anchorAtRightOrBottom) != 0) - s << 'r'; - else if ((mode & anchorAtCentre) != 0) - s << 'c'; + return s.toUTF8(); } -void PositionedRectangle::addSizeDescription (String& s, const uint8 mode, const double value) const throw() +void Path::restoreFromString (const String& stringVersion) { - if (mode == proportionalSize) - s << (roundToInt (value * 100000.0) / 1000.0) << '%'; - else if (mode == parentSizeMinusAbsolute) - s << (roundToInt (value * 100.0) / 100.0) << 'M'; - else - s << (roundToInt (value * 100.0) / 100.0); -} + clear(); + setUsingNonZeroWinding (true); -void PositionedRectangle::decodePosString (const String& s, uint8& mode, double& value) throw() -{ - if (s.containsChar ('r')) - mode = anchorAtRightOrBottom; - else if (s.containsChar ('c')) - mode = anchorAtCentre; - else - mode = anchorAtLeftOrTop; + const juce_wchar* t = stringVersion; + juce_wchar marker = 'm'; + int numValues = 2; + float values [6]; - if (s.containsChar ('%')) - { - mode |= proportionOfParentSize; - value = s.removeCharacters ("%rcRC").getDoubleValue() / 100.0; - } - else + for (;;) { - if (s.containsChar ('R')) - mode |= absoluteFromParentBottomRight; - else if (s.containsChar ('C')) - mode |= absoluteFromParentCentre; + const String token (PathHelpers::nextToken (t)); + const juce_wchar firstChar = token[0]; + int startNum = 0; + + if (firstChar == 0) + break; + + if (firstChar == 'm' || firstChar == 'l') + { + marker = firstChar; + numValues = 2; + } + else if (firstChar == 'q') + { + marker = firstChar; + numValues = 4; + } + else if (firstChar == 'c') + { + marker = firstChar; + numValues = 6; + } + else if (firstChar == 'z') + { + marker = firstChar; + numValues = 0; + } + else if (firstChar == 'a') + { + setUsingNonZeroWinding (false); + continue; + } else - mode |= absoluteFromParentTopLeft; + { + ++startNum; + values [0] = token.getFloatValue(); + } + + for (int i = startNum; i < numValues; ++i) + values [i] = PathHelpers::nextToken (t).getFloatValue(); + + switch (marker) + { + case 'm': + startNewSubPath (values[0], values[1]); + break; + + case 'l': + lineTo (values[0], values[1]); + break; + + case 'q': + quadraticTo (values[0], values[1], + values[2], values[3]); + break; + + case 'c': + cubicTo (values[0], values[1], + values[2], values[3], + values[4], values[5]); + break; + + case 'z': + closeSubPath(); + break; - value = s.removeCharacters ("rcRC").getDoubleValue(); + default: + jassertfalse; // illegal string format? + break; + } } } -void PositionedRectangle::decodeSizeString (const String& s, uint8& mode, double& value) throw() +Path::Iterator::Iterator (const Path& path_) + : path (path_), + index (0) { - if (s.containsChar ('%')) - { - mode = proportionalSize; - value = s.upToFirstOccurrenceOf ("%", false, false).getDoubleValue() / 100.0; - } - else if (s.containsChar ('M')) - { - mode = parentSizeMinusAbsolute; - value = s.getDoubleValue(); - } - else - { - mode = absoluteSize; - value = s.getDoubleValue(); - } } -void PositionedRectangle::applyPosAndSize (double& xOut, double& wOut, - const double x_, const double w_, - const uint8 xMode_, const uint8 wMode_, - const int parentPos, - const int parentSize) const throw() +Path::Iterator::~Iterator() { - if (wMode_ == proportionalSize) - wOut = roundToInt (w_ * parentSize); - else if (wMode_ == parentSizeMinusAbsolute) - wOut = jmax (0, parentSize - roundToInt (w_)); - else - wOut = roundToInt (w_); - - if ((xMode_ & proportionOfParentSize) != 0) - xOut = parentPos + x_ * parentSize; - else if ((xMode_ & absoluteFromParentBottomRight) != 0) - xOut = (parentPos + parentSize) - x_; - else if ((xMode_ & absoluteFromParentCentre) != 0) - xOut = x_ + (parentPos + parentSize / 2); - else - xOut = x_ + parentPos; - - if ((xMode_ & anchorAtRightOrBottom) != 0) - xOut -= wOut; - else if ((xMode_ & anchorAtCentre) != 0) - xOut -= wOut / 2; } -void PositionedRectangle::updatePosAndSize (double& xOut, double& wOut, - double x_, const double w_, - const uint8 xMode_, const uint8 wMode_, - const int parentPos, - const int parentSize) const throw() +bool Path::Iterator::next() { - if (wMode_ == proportionalSize) + const float* const elements = path.data.elements; + + if (index < path.numElements) { - if (parentSize > 0) - wOut = w_ / parentSize; - } - else if (wMode_ == parentSizeMinusAbsolute) - wOut = parentSize - w_; - else - wOut = w_; + const float type = elements [index++]; - if ((xMode_ & anchorAtRightOrBottom) != 0) - x_ += w_; - else if ((xMode_ & anchorAtCentre) != 0) - x_ += w_ / 2; + if (type == moveMarker) + { + elementType = startNewSubPath; + x1 = elements [index++]; + y1 = elements [index++]; + } + else if (type == lineMarker) + { + elementType = lineTo; + x1 = elements [index++]; + y1 = elements [index++]; + } + else if (type == quadMarker) + { + elementType = quadraticTo; + x1 = elements [index++]; + y1 = elements [index++]; + x2 = elements [index++]; + y2 = elements [index++]; + } + else if (type == cubicMarker) + { + elementType = cubicTo; + x1 = elements [index++]; + y1 = elements [index++]; + x2 = elements [index++]; + y2 = elements [index++]; + x3 = elements [index++]; + y3 = elements [index++]; + } + else if (type == closeSubPathMarker) + { + elementType = closePath; + } - if ((xMode_ & proportionOfParentSize) != 0) - { - if (parentSize > 0) - xOut = (x_ - parentPos) / parentSize; + return true; } - else if ((xMode_ & absoluteFromParentBottomRight) != 0) - xOut = (parentPos + parentSize) - x_; - else if ((xMode_ & absoluteFromParentCentre) != 0) - xOut = x_ - (parentPos + parentSize / 2); - else - xOut = x_ - parentPos; + + return false; } END_JUCE_NAMESPACE -/*** End of inlined file: juce_PositionedRectangle.cpp ***/ +/*** End of inlined file: juce_Path.cpp ***/ -/*** Start of inlined file: juce_RectangleList.cpp ***/ +/*** Start of inlined file: juce_PathIterator.cpp ***/ BEGIN_JUCE_NAMESPACE -RectangleList::RectangleList() throw() -{ -} - -RectangleList::RectangleList (const Rectangle& rect) -{ - if (! rect.isEmpty()) - rects.add (rect); -} - -RectangleList::RectangleList (const RectangleList& other) - : rects (other.rects) -{ -} - -RectangleList& RectangleList::operator= (const RectangleList& other) -{ - rects = other.rects; - return *this; -} +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("t", on) +#endif -RectangleList::~RectangleList() +PathFlatteningIterator::PathFlatteningIterator (const Path& path_, + const AffineTransform& transform_, + float tolerence_) + : x2 (0), + y2 (0), + closesSubPath (false), + subPathIndex (-1), + path (path_), + transform (transform_), + points (path_.data.elements), + tolerence (tolerence_ * tolerence_), + subPathCloseX (0), + subPathCloseY (0), + isIdentityTransform (transform_.isIdentity()), + stackBase (32), + index (0), + stackSize (32) { + stackPos = stackBase; } -void RectangleList::clear() +PathFlatteningIterator::~PathFlatteningIterator() { - rects.clearQuick(); } -const Rectangle RectangleList::getRectangle (const int index) const throw() +bool PathFlatteningIterator::next() { - if (((unsigned int) index) < (unsigned int) rects.size()) - return rects.getReference (index); - - return Rectangle(); -} + x1 = x2; + y1 = y2; -bool RectangleList::isEmpty() const throw() -{ - return rects.size() == 0; -} + float x3 = 0; + float y3 = 0; + float x4 = 0; + float y4 = 0; + float type; -RectangleList::Iterator::Iterator (const RectangleList& list) throw() - : current (0), - owner (list), - index (list.rects.size()) -{ -} + for (;;) + { + if (stackPos == stackBase) + { + if (index >= path.numElements) + { + return false; + } + else + { + type = points [index++]; -RectangleList::Iterator::~Iterator() -{ -} + if (type != Path::closeSubPathMarker) + { + x2 = points [index++]; + y2 = points [index++]; -bool RectangleList::Iterator::next() throw() -{ - if (--index >= 0) - { - current = & (owner.rects.getReference (index)); - return true; - } + if (type == Path::quadMarker) + { + x3 = points [index++]; + y3 = points [index++]; - return false; -} + if (! isIdentityTransform) + transform.transformPoints (x2, y2, x3, y3); + } + else if (type == Path::cubicMarker) + { + x3 = points [index++]; + y3 = points [index++]; + x4 = points [index++]; + y4 = points [index++]; -void RectangleList::add (const Rectangle& rect) -{ - if (! rect.isEmpty()) - { - if (rects.size() == 0) - { - rects.add (rect); + if (! isIdentityTransform) + transform.transformPoints (x2, y2, x3, y3, x4, y4); + } + else + { + if (! isIdentityTransform) + transform.transformPoint (x2, y2); + } + } + } } else { - bool anyOverlaps = false; + type = *--stackPos; - int i; - for (i = rects.size(); --i >= 0;) + if (type != Path::closeSubPathMarker) { - Rectangle& ourRect = rects.getReference (i); + x2 = *--stackPos; + y2 = *--stackPos; - if (rect.intersects (ourRect)) + if (type == Path::quadMarker) { - if (rect.contains (ourRect)) - rects.remove (i); - else if (! ourRect.reduceIfPartlyContainedIn (rect)) - anyOverlaps = true; + x3 = *--stackPos; + y3 = *--stackPos; + } + else if (type == Path::cubicMarker) + { + x3 = *--stackPos; + y3 = *--stackPos; + x4 = *--stackPos; + y4 = *--stackPos; } } + } - if (anyOverlaps && rects.size() > 0) - { - RectangleList r (rect); - - for (i = rects.size(); --i >= 0;) - { - const Rectangle& ourRect = rects.getReference (i); + if (type == Path::lineMarker) + { + ++subPathIndex; - if (rect.intersects (ourRect)) - { - r.subtract (ourRect); + closesSubPath = (stackPos == stackBase) + && (index < path.numElements) + && (points [index] == Path::closeSubPathMarker) + && x2 == subPathCloseX + && y2 == subPathCloseY; - if (r.rects.size() == 0) - return; - } - } + return true; + } + else if (type == Path::quadMarker) + { + const size_t offset = (size_t) (stackPos - stackBase); - for (i = r.getNumRectangles(); --i >= 0;) - rects.add (r.rects.getReference (i)); - } - else + if (offset >= stackSize - 10) { - rects.add (rect); + stackSize <<= 1; + stackBase.realloc (stackSize); + stackPos = stackBase + offset; } - } - } -} -void RectangleList::addWithoutMerging (const Rectangle& rect) -{ - if (! rect.isEmpty()) - rects.add (rect); -} + const float dx1 = x1 - x2; + const float dy1 = y1 - y2; + const float dx2 = x2 - x3; + const float dy2 = y2 - y3; -void RectangleList::add (const int x, const int y, const int w, const int h) -{ - if (rects.size() == 0) - { - if (w > 0 && h > 0) - rects.add (Rectangle (x, y, w, h)); - } - else - { - add (Rectangle (x, y, w, h)); - } -} + const float m1x = (x1 + x2) * 0.5f; + const float m1y = (y1 + y2) * 0.5f; + const float m2x = (x2 + x3) * 0.5f; + const float m2y = (y2 + y3) * 0.5f; + const float m3x = (m1x + m2x) * 0.5f; + const float m3y = (m1y + m2y) * 0.5f; -void RectangleList::add (const RectangleList& other) -{ - for (int i = 0; i < other.rects.size(); ++i) - add (other.rects.getReference (i)); -} + if (dx1*dx1 + dy1*dy1 + dx2*dx2 + dy2*dy2 > tolerence) + { + *stackPos++ = y3; + *stackPos++ = x3; + *stackPos++ = m2y; + *stackPos++ = m2x; + *stackPos++ = Path::quadMarker; -void RectangleList::subtract (const Rectangle& rect) -{ - const int originalNumRects = rects.size(); + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = m1y; + *stackPos++ = m1x; + *stackPos++ = Path::quadMarker; + } + else + { + *stackPos++ = y3; + *stackPos++ = x3; + *stackPos++ = Path::lineMarker; - if (originalNumRects > 0) - { - const int x1 = rect.x; - const int y1 = rect.y; - const int x2 = x1 + rect.w; - const int y2 = y1 + rect.h; + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = Path::lineMarker; + } - for (int i = getNumRectangles(); --i >= 0;) + jassert (stackPos < stackBase + stackSize); + } + else if (type == Path::cubicMarker) { - Rectangle& r = rects.getReference (i); - - const int rx1 = r.x; - const int ry1 = r.y; - const int rx2 = rx1 + r.w; - const int ry2 = ry1 + r.h; + const size_t offset = (size_t) (stackPos - stackBase); - if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2)) + if (offset >= stackSize - 16) { - if (x1 > rx1 && x1 < rx2) - { - if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2) - { - r.w = x1 - rx1; - } - else - { - r.x = x1; - r.w = rx2 - x1; + stackSize <<= 1; + stackBase.realloc (stackSize); + stackPos = stackBase + offset; + } - rects.insert (i + 1, Rectangle (rx1, ry1, x1 - rx1, ry2 - ry1)); - i += 2; - } - } - else if (x2 > rx1 && x2 < rx2) - { - r.x = x2; - r.w = rx2 - x2; + const float dx1 = x1 - x2; + const float dy1 = y1 - y2; + const float dx2 = x2 - x3; + const float dy2 = y2 - y3; + const float dx3 = x3 - x4; + const float dy3 = y3 - y4; - if (y1 > ry1 || y2 < ry2 || x1 > rx1) - { - rects.insert (i + 1, Rectangle (rx1, ry1, x2 - rx1, ry2 - ry1)); - i += 2; - } - } - else if (y1 > ry1 && y1 < ry2) - { - if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2) - { - r.h = y1 - ry1; - } - else - { - r.y = y1; - r.h = ry2 - y1; + const float m1x = (x1 + x2) * 0.5f; + const float m1y = (y1 + y2) * 0.5f; + const float m2x = (x3 + x2) * 0.5f; + const float m2y = (y3 + y2) * 0.5f; + const float m3x = (x3 + x4) * 0.5f; + const float m3y = (y3 + y4) * 0.5f; + const float m4x = (m1x + m2x) * 0.5f; + const float m4y = (m1y + m2y) * 0.5f; + const float m5x = (m3x + m2x) * 0.5f; + const float m5y = (m3y + m2y) * 0.5f; - rects.insert (i + 1, Rectangle (rx1, ry1, rx2 - rx1, y1 - ry1)); - i += 2; - } - } - else if (y2 > ry1 && y2 < ry2) - { - r.y = y2; - r.h = ry2 - y2; + if (dx1*dx1 + dy1*dy1 + dx2*dx2 + + dy2*dy2 + dx3*dx3 + dy3*dy3 > tolerence) + { + *stackPos++ = y4; + *stackPos++ = x4; + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = m5y; + *stackPos++ = m5x; + *stackPos++ = Path::cubicMarker; - if (x1 > rx1 || x2 < rx2 || y1 > ry1) - { - rects.insert (i + 1, Rectangle (rx1, ry1, rx2 - rx1, y2 - ry1)); - i += 2; - } - } - else - { - rects.remove (i); - } + *stackPos++ = (m4y + m5y) * 0.5f; + *stackPos++ = (m4x + m5x) * 0.5f; + *stackPos++ = m4y; + *stackPos++ = m4x; + *stackPos++ = m1y; + *stackPos++ = m1x; + *stackPos++ = Path::cubicMarker; } - } - } -} - -bool RectangleList::subtract (const RectangleList& otherList) -{ - for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;) - subtract (otherList.rects.getReference (i)); + else + { + *stackPos++ = y4; + *stackPos++ = x4; + *stackPos++ = Path::lineMarker; - return rects.size() > 0; -} + *stackPos++ = m5y; + *stackPos++ = m5x; + *stackPos++ = Path::lineMarker; -bool RectangleList::clipTo (const Rectangle& rect) -{ - bool notEmpty = false; + *stackPos++ = m4y; + *stackPos++ = m4x; + *stackPos++ = Path::lineMarker; + } + } + else if (type == Path::closeSubPathMarker) + { + if (x2 != subPathCloseX || y2 != subPathCloseY) + { + x1 = x2; + y1 = y2; + x2 = subPathCloseX; + y2 = subPathCloseY; + closesSubPath = true; - if (rect.isEmpty()) - { - clear(); - } - else - { - for (int i = rects.size(); --i >= 0;) + return true; + } + } + else { - Rectangle& r = rects.getReference (i); + jassert (type == Path::moveMarker); - if (! rect.intersectRectangle (r.x, r.y, r.w, r.h)) - rects.remove (i); - else - notEmpty = true; + subPathIndex = -1; + subPathCloseX = x1 = x2; + subPathCloseY = y1 = y2; } } - - return notEmpty; } -bool RectangleList::clipTo (const RectangleList& other) -{ - if (rects.size() == 0) - return false; - - RectangleList result; - - for (int j = 0; j < rects.size(); ++j) - { - const Rectangle& rect = rects.getReference (j); +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("", on) // resets optimisations to the project defaults +#endif - for (int i = other.rects.size(); --i >= 0;) - { - Rectangle r (other.rects.getReference (i)); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_PathIterator.cpp ***/ - if (rect.intersectRectangle (r.x, r.y, r.w, r.h)) - result.rects.add (r); - } - } - swapWith (result); +/*** Start of inlined file: juce_PathStrokeType.cpp ***/ +BEGIN_JUCE_NAMESPACE - return ! isEmpty(); +PathStrokeType::PathStrokeType (const float strokeThickness, + const JointStyle jointStyle_, + const EndCapStyle endStyle_) throw() + : thickness (strokeThickness), + jointStyle (jointStyle_), + endStyle (endStyle_) +{ } -bool RectangleList::getIntersectionWith (const Rectangle& rect, RectangleList& destRegion) const +PathStrokeType::PathStrokeType (const PathStrokeType& other) throw() + : thickness (other.thickness), + jointStyle (other.jointStyle), + endStyle (other.endStyle) { - destRegion.clear(); +} - if (! rect.isEmpty()) - { - for (int i = rects.size(); --i >= 0;) - { - Rectangle r (rects.getReference (i)); +PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other) throw() +{ + thickness = other.thickness; + jointStyle = other.jointStyle; + endStyle = other.endStyle; + return *this; +} - if (rect.intersectRectangle (r.x, r.y, r.w, r.h)) - destRegion.rects.add (r); - } - } +PathStrokeType::~PathStrokeType() throw() +{ +} - return destRegion.rects.size() > 0; +bool PathStrokeType::operator== (const PathStrokeType& other) const throw() +{ + return thickness == other.thickness + && jointStyle == other.jointStyle + && endStyle == other.endStyle; } -void RectangleList::swapWith (RectangleList& otherList) throw() +bool PathStrokeType::operator!= (const PathStrokeType& other) const throw() { - rects.swapWithArray (otherList.rects); + return ! operator== (other); } -void RectangleList::consolidate() +namespace PathStrokeHelpers { - int i; - for (i = 0; i < getNumRectangles() - 1; ++i) + static bool lineIntersection (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4, + float& intersectionX, + float& intersectionY, + float& distanceBeyondLine1EndSquared) throw() { - Rectangle& r = rects.getReference (i); - const int rx1 = r.x; - const int ry1 = r.y; - const int rx2 = rx1 + r.w; - const int ry2 = ry1 + r.h; - - for (int j = rects.size(); --j > i;) + if (x2 != x3 || y2 != y3) { - Rectangle& r2 = rects.getReference (j); - const int jrx1 = r2.x; - const int jry1 = r2.y; - const int jrx2 = jrx1 + r2.w; - const int jry2 = jry1 + r2.h; + const float dx1 = x2 - x1; + const float dy1 = y2 - y1; + const float dx2 = x4 - x3; + const float dy2 = y4 - y3; + const float divisor = dx1 * dy2 - dx2 * dy1; - // if the vertical edges of any blocks are touching and their horizontals don't - // line up, split them horizontally.. - if (jrx1 == rx2 || jrx2 == rx1) + if (divisor == 0) { - if (jry1 > ry1 && jry1 < ry2) + if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0))) { - r.h = jry1 - ry1; - rects.add (Rectangle (rx1, jry1, rx2 - rx1, ry2 - jry1)); - i = -1; - break; - } + if (dy1 == 0 && dy2 != 0) + { + const float along = (y1 - y3) / dy2; + intersectionX = x3 + along * dx2; + intersectionY = y1; - if (jry2 > ry1 && jry2 < ry2) - { - r.h = jry2 - ry1; - rects.add (Rectangle (rx1, jry2, rx2 - rx1, ry2 - jry2)); - i = -1; - break; - } - else if (ry1 > jry1 && ry1 < jry2) - { - r2.h = ry1 - jry1; - rects.add (Rectangle (jrx1, ry1, jrx2 - jrx1, jry2 - ry1)); - i = -1; - break; - } - else if (ry2 > jry1 && ry2 < jry2) - { - r2.h = ry2 - jry1; - rects.add (Rectangle (jrx1, ry2, jrx2 - jrx1, jry2 - ry2)); - i = -1; - break; - } - } - } - } + distanceBeyondLine1EndSquared = intersectionX - x2; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if ((x2 > x1) == (intersectionX < x2)) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - for (i = 0; i < rects.size() - 1; ++i) - { - Rectangle& r = rects.getReference (i); + return along >= 0 && along <= 1.0f; + } + else if (dy2 == 0 && dy1 != 0) + { + const float along = (y3 - y1) / dy1; + intersectionX = x1 + along * dx1; + intersectionY = y3; - for (int j = rects.size(); --j > i;) - { - if (r.enlargeIfAdjacent (rects.getReference (j))) - { - rects.remove (j); - i = -1; - break; - } - } - } -} + distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if (along < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; -bool RectangleList::containsPoint (const int x, const int y) const throw() -{ - for (int i = getNumRectangles(); --i >= 0;) - if (rects.getReference (i).contains (x, y)) - return true; + return along >= 0 && along <= 1.0f; + } + else if (dx1 == 0 && dx2 != 0) + { + const float along = (x1 - x3) / dx2; + intersectionX = x1; + intersectionY = y3 + along * dy2; - return false; -} + distanceBeyondLine1EndSquared = intersectionY - y2; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; -bool RectangleList::containsRectangle (const Rectangle& rectangleToCheck) const -{ - if (rects.size() > 1) - { - RectangleList r (rectangleToCheck); + if ((y2 > y1) == (intersectionY < y2)) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - for (int i = rects.size(); --i >= 0;) - { - r.subtract (rects.getReference (i)); + return along >= 0 && along <= 1.0f; + } + else if (dx2 == 0 && dx1 != 0) + { + const float along = (x3 - x1) / dx1; + intersectionX = x3; + intersectionY = y1 + along * dy1; - if (r.rects.size() == 0) - return true; - } - } - else if (rects.size() > 0) - { - return rects.getReference (0).contains (rectangleToCheck); - } + distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if (along < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - return false; -} + return along >= 0 && along <= 1.0f; + } + } -bool RectangleList::intersectsRectangle (const Rectangle& rectangleToCheck) const throw() -{ - for (int i = rects.size(); --i >= 0;) - if (rects.getReference (i).intersects (rectangleToCheck)) - return true; + intersectionX = 0.5f * (x2 + x3); + intersectionY = 0.5f * (y2 + y3); - return false; -} + distanceBeyondLine1EndSquared = 0.0f; + return false; + } + else + { + const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; -bool RectangleList::intersects (const RectangleList& other) const throw() -{ - for (int i = rects.size(); --i >= 0;) - if (other.intersectsRectangle (rects.getReference (i))) - return true; + intersectionX = x1 + along1 * dx1; + intersectionY = y1 + along1 * dy1; - return false; -} + if (along1 >= 0 && along1 <= 1.0f) + { + const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); -const Rectangle RectangleList::getBounds() const throw() -{ - if (rects.size() <= 1) - { - if (rects.size() == 0) - return Rectangle(); - else - return rects.getReference (0); - } - else - { - const Rectangle& r = rects.getReference (0); + if (along2 >= 0 && along2 <= divisor) + { + distanceBeyondLine1EndSquared = 0.0f; + return true; + } + } - int minX = r.x; - int minY = r.y; - int maxX = minX + r.w; - int maxY = minY + r.h; + distanceBeyondLine1EndSquared = along1 - 1.0f; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); - for (int i = rects.size(); --i > 0;) - { - const Rectangle& r2 = rects.getReference (i); + if (along1 < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - minX = jmin (minX, r2.x); - minY = jmin (minY, r2.y); - maxX = jmax (maxX, r2.getRight()); - maxY = jmax (maxY, r2.getBottom()); + return false; + } } - return Rectangle (minX, minY, maxX - minX, maxY - minY); + intersectionX = x2; + intersectionY = y2; + + distanceBeyondLine1EndSquared = 0.0f; + return true; } -} -void RectangleList::offsetAll (const int dx, const int dy) throw() -{ - for (int i = rects.size(); --i >= 0;) + static void addEdgeAndJoint (Path& destPath, + const PathStrokeType::JointStyle style, + const float maxMiterExtensionSquared, const float width, + const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4, + const float midX, const float midY) { - Rectangle& r = rects.getReference (i); + if (style == PathStrokeType::beveled + || (x3 == x4 && y3 == y4) + || (x1 == x2 && y1 == y2)) + { + destPath.lineTo (x2, y2); + destPath.lineTo (x3, y3); + } + else + { + float jx, jy, distanceBeyondLine1EndSquared; - r.x += dx; - r.y += dy; - } -} + // if they intersect, use this point.. + if (lineIntersection (x1, y1, x2, y2, + x3, y3, x4, y4, + jx, jy, distanceBeyondLine1EndSquared)) + { + destPath.lineTo (jx, jy); + } + else + { + if (style == PathStrokeType::mitered) + { + if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared + && distanceBeyondLine1EndSquared > 0.0f) + { + destPath.lineTo (jx, jy); + } + else + { + // the end sticks out too far, so just use a blunt joint + destPath.lineTo (x2, y2); + destPath.lineTo (x3, y3); + } + } + else + { + // curved joints + float angle1 = std::atan2 (x2 - midX, y2 - midY); + float angle2 = std::atan2 (x3 - midX, y3 - midY); + const float angleIncrement = 0.1f; -const Path RectangleList::toPath() const -{ - Path p; + destPath.lineTo (x2, y2); - for (int i = rects.size(); --i >= 0;) - { - const Rectangle& r = rects.getReference (i); + if (std::abs (angle1 - angle2) > angleIncrement) + { + if (angle2 > angle1 + float_Pi + || (angle2 < angle1 && angle2 >= angle1 - float_Pi)) + { + if (angle2 > angle1) + angle2 -= float_Pi * 2.0f; - p.addRectangle ((float) r.x, - (float) r.y, - (float) r.w, - (float) r.h); - } + jassert (angle1 <= angle2 + float_Pi); - return p; -} + angle1 -= angleIncrement; + while (angle1 > angle2) + { + destPath.lineTo (midX + width * std::sin (angle1), + midY + width * std::cos (angle1)); -END_JUCE_NAMESPACE -/*** End of inlined file: juce_RectangleList.cpp ***/ + angle1 -= angleIncrement; + } + } + else + { + if (angle1 > angle2) + angle1 -= float_Pi * 2.0f; + jassert (angle1 >= angle2 - float_Pi); -/*** Start of inlined file: juce_RelativeCoordinate.cpp ***/ -BEGIN_JUCE_NAMESPACE + angle1 += angleIncrement; + while (angle1 < angle2) + { + destPath.lineTo (midX + width * std::sin (angle1), + midY + width * std::cos (angle1)); -namespace RelativeCoordinateHelpers -{ - static bool isOrigin (const String& name) - { - return name.isEmpty() - || name == RelativeCoordinate::Strings::parentLeft - || name == RelativeCoordinate::Strings::parentTop; - } + angle1 += angleIncrement; + } + } + } - static const String getExtentAnchorName (const bool isHorizontal) throw() - { - return isHorizontal ? RelativeCoordinate::Strings::parentRight - : RelativeCoordinate::Strings::parentBottom; + destPath.lineTo (x3, y3); + } + } + } } - static const String getObjectName (const String& fullName) + static void addLineEnd (Path& destPath, + const PathStrokeType::EndCapStyle style, + const float x1, const float y1, + const float x2, const float y2, + const float width) { - return fullName.upToFirstOccurrenceOf (".", false, false); - } + if (style == PathStrokeType::butt) + { + destPath.lineTo (x2, y2); + } + else + { + float offx1, offy1, offx2, offy2; - static const String getEdgeName (const String& fullName) - { - return fullName.fromFirstOccurrenceOf (".", false, false); - } + float dx = x2 - x1; + float dy = y2 - y1; + const float len = juce_hypotf (dx, dy); - static const RelativeCoordinate findCoordinate (const String& name, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) - { - return nameFinder != 0 ? nameFinder->findNamedCoordinate (getObjectName (name), getEdgeName (name)) - : RelativeCoordinate(); - } + if (len == 0) + { + offx1 = offx2 = x1; + offy1 = offy2 = y1; + } + else + { + const float offset = width / len; + dx *= offset; + dy *= offset; - struct RecursionException : public std::runtime_error - { - RecursionException() : std::runtime_error ("Recursive RelativeCoordinate expression") - { + offx1 = x1 + dy; + offy1 = y1 - dx; + offx2 = x2 + dy; + offy2 = y2 - dx; + } + + if (style == PathStrokeType::square) + { + // sqaure ends + destPath.lineTo (offx1, offy1); + destPath.lineTo (offx2, offy2); + destPath.lineTo (x2, y2); + } + else + { + // rounded ends + const float midx = (offx1 + offx2) * 0.5f; + const float midy = (offy1 + offy2) * 0.5f; + + destPath.cubicTo (x1 + (offx1 - x1) * 0.55f, y1 + (offy1 - y1) * 0.55f, + offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f, + midx, midy); + + destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f, + offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f, + x2, y2); + } } - }; + } - static void skipWhitespace (const juce_wchar* const s, int& i) + struct Arrowhead { - while (CharacterFunctions::isWhitespace (s[i])) - ++i; - } + float startWidth, startLength; + float endWidth, endLength; + }; - static void skipComma (const juce_wchar* const s, int& i) + static void addArrowhead (Path& destPath, + const float x1, const float y1, + const float x2, const float y2, + const float tipX, const float tipY, + const float width, + const float arrowheadWidth) { - skipWhitespace (s, i); - if (s[i] == ',') - ++i; + Line line (x1, y1, x2, y2); + destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0)); + destPath.lineTo (tipX, tipY); + destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0)); + destPath.lineTo (x2, y2); } - static const String readAnchorName (const juce_wchar* const s, int& i) + struct LineSection { - skipWhitespace (s, i); + float x1, y1, x2, y2; // original line + float lx1, ly1, lx2, ly2; // the left-hand stroke + float rx1, ry1, rx2, ry2; // the right-hand stroke + }; - if (CharacterFunctions::isLetter (s[i]) || s[i] == '_') + static void shortenSubPath (Array& subPath, float amountAtStart, float amountAtEnd) + { + while (amountAtEnd > 0 && subPath.size() > 0) { - int start = i; - while (CharacterFunctions::isLetterOrDigit (s[i]) || s[i] == '_' || s[i] == '.') - ++i; + LineSection& l = subPath.getReference (subPath.size() - 1); + float dx = l.rx2 - l.rx1; + float dy = l.ry2 - l.ry1; + const float len = juce_hypotf (dx, dy); - return String (s + start, i - start); + if (len <= amountAtEnd && subPath.size() > 1) + { + LineSection& prev = subPath.getReference (subPath.size() - 2); + prev.x2 = l.x2; + prev.y2 = l.y2; + subPath.removeLast(); + amountAtEnd -= len; + } + else + { + const float prop = jmin (0.9999f, amountAtEnd / len); + dx *= prop; + dy *= prop; + l.rx1 += dx; + l.ry1 += dy; + l.lx2 += dx; + l.ly2 += dy; + break; + } } - return String::empty; + while (amountAtStart > 0 && subPath.size() > 0) + { + LineSection& l = subPath.getReference (0); + float dx = l.rx2 - l.rx1; + float dy = l.ry2 - l.ry1; + const float len = juce_hypotf (dx, dy); + + if (len <= amountAtStart && subPath.size() > 1) + { + LineSection& next = subPath.getReference (1); + next.x1 = l.x1; + next.y1 = l.y1; + subPath.remove (0); + amountAtStart -= len; + } + else + { + const float prop = jmin (0.9999f, amountAtStart / len); + dx *= prop; + dy *= prop; + l.rx2 -= dx; + l.ry2 -= dy; + l.lx1 -= dx; + l.ly1 -= dy; + break; + } + } } - static double readNumber (const juce_wchar* const s, int& i) + static void addSubPath (Path& destPath, Array& subPath, + const bool isClosed, const float width, const float maxMiterExtensionSquared, + const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle, + const Arrowhead* const arrowhead) { - skipWhitespace (s, i); + jassert (subPath.size() > 0); - int start = i; - if (CharacterFunctions::isDigit (s[i]) || s[i] == '.' || s[i] == '-') - ++i; + if (arrowhead != 0) + shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength); - while (CharacterFunctions::isDigit (s[i]) || s[i] == '.') - ++i; + const LineSection& firstLine = subPath.getReference (0); - if ((s[i] == 'e' || s[i] == 'E') - && (CharacterFunctions::isDigit (s[i + 1]) - || s[i + 1] == '-' - || s[i + 1] == '+')) + float lastX1 = firstLine.lx1; + float lastY1 = firstLine.ly1; + float lastX2 = firstLine.lx2; + float lastY2 = firstLine.ly2; + + if (isClosed) { - i += 2; + destPath.startNewSubPath (lastX1, lastY1); + } + else + { + destPath.startNewSubPath (firstLine.rx2, firstLine.ry2); - while (CharacterFunctions::isDigit (s[i])) - ++i; + if (arrowhead != 0) + addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1, + width, arrowhead->startWidth); + else + addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width); } - const double value = String (s + start, i - start).getDoubleValue(); - while (CharacterFunctions::isWhitespace (s[i]) || s[i] == ',') - ++i; + int i; + for (i = 1; i < subPath.size(); ++i) + { + const LineSection& l = subPath.getReference (i); - return value; - } + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.lx1, l.ly1, l.lx2, l.ly2, + l.x1, l.y1); - static const RelativeCoordinate readNextCoordinate (const juce_wchar* const s, int& i, const bool isHorizontal) - { - String anchor1 (readAnchorName (s, i)); - double value = 0; + lastX1 = l.lx1; + lastY1 = l.ly1; + lastX2 = l.lx2; + lastY2 = l.ly2; + } - if (anchor1.isNotEmpty()) + const LineSection& lastLine = subPath.getReference (subPath.size() - 1); + + if (isClosed) { - skipWhitespace (s, i); + const LineSection& l = subPath.getReference (0); - if (s[i] == '+') - value = readNumber (s, ++i); - else if (s[i] == '-') - value = -readNumber (s, ++i); + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.lx1, l.ly1, l.lx2, l.ly2, + l.x1, l.y1); - return RelativeCoordinate (value, anchor1); + destPath.closeSubPath(); + destPath.startNewSubPath (lastLine.rx1, lastLine.ry1); } else { - value = readNumber (s, i); - skipWhitespace (s, i); - - if (s[i] == '%') - { - value /= 100.0; - skipWhitespace (s, ++i); - String anchor2; + destPath.lineTo (lastX2, lastY2); - if (s[i] == '*') - { - anchor1 = readAnchorName (s, ++i); + if (arrowhead != 0) + addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2, + width, arrowhead->endWidth); + else + addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width); + } - skipWhitespace (s, i); + lastX1 = lastLine.rx1; + lastY1 = lastLine.ry1; + lastX2 = lastLine.rx2; + lastY2 = lastLine.ry2; - if (s[i] == '-' && s[i + 1] == '>') - { - i += 2; - anchor2 = readAnchorName (s, i); - } - else - { - anchor2 = anchor1; - anchor1 = String::empty; - } - } - else - { - anchor1 = String::empty; - anchor2 = getExtentAnchorName (isHorizontal); - } + for (i = subPath.size() - 1; --i >= 0;) + { + const LineSection& l = subPath.getReference (i); - return RelativeCoordinate (value, anchor1, anchor2); - } + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.rx1, l.ry1, l.rx2, l.ry2, + l.x2, l.y2); - return RelativeCoordinate (value); + lastX1 = l.rx1; + lastY1 = l.ry1; + lastX2 = l.rx2; + lastY2 = l.ry2; } - } - static const String limitedAccuracyString (const double n) - { - if (! (n < -0.001 || n > 0.001)) // to detect NaN and inf as well as for rounding - return "0"; + if (isClosed) + { + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2, + lastLine.x2, lastLine.y2); + } + else + { + // do the last line + destPath.lineTo (lastX2, lastY2); + } - return String (n, 3).trimCharactersAtEnd ("0").trimCharactersAtEnd ("."); + destPath.closeSubPath(); } -} - -const String RelativeCoordinate::Strings::parent ("parent"); -const String RelativeCoordinate::Strings::left ("left"); -const String RelativeCoordinate::Strings::right ("right"); -const String RelativeCoordinate::Strings::top ("top"); -const String RelativeCoordinate::Strings::bottom ("bottom"); -const String RelativeCoordinate::Strings::parentLeft ("parent.left"); -const String RelativeCoordinate::Strings::parentTop ("parent.top"); -const String RelativeCoordinate::Strings::parentRight ("parent.right"); -const String RelativeCoordinate::Strings::parentBottom ("parent.bottom"); - -RelativeCoordinate::RelativeCoordinate() - : value (0) -{ -} - -RelativeCoordinate::RelativeCoordinate (const double absoluteDistanceFromOrigin) - : value (absoluteDistanceFromOrigin) -{ -} - -RelativeCoordinate::RelativeCoordinate (const double absoluteDistance, const String& source) - : anchor1 (source.trim()), - value (absoluteDistance) -{ -} -RelativeCoordinate::RelativeCoordinate (const double relativeProportion, const String& pos1, const String& pos2) - : anchor1 (pos1.trim()), - anchor2 (pos2.trim()), - value (relativeProportion) -{ -} + static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle, + const PathStrokeType::EndCapStyle endStyle, + Path& destPath, const Path& source, + const AffineTransform& transform, + const float extraAccuracy, const Arrowhead* const arrowhead) + { + if (thickness <= 0) + { + destPath.clear(); + return; + } -RelativeCoordinate::RelativeCoordinate (const String& s, const bool isHorizontal) - : value (0) -{ - int i = 0; - *this = RelativeCoordinateHelpers::readNextCoordinate (s, i, isHorizontal); -} + const Path* sourcePath = &source; + Path temp; -RelativeCoordinate::~RelativeCoordinate() -{ -} + if (sourcePath == &destPath) + { + destPath.swapWithPath (temp); + sourcePath = &temp; + } + else + { + destPath.clear(); + } -bool RelativeCoordinate::operator== (const RelativeCoordinate& other) const throw() -{ - return value == other.value && anchor1 == other.anchor1 && anchor2 == other.anchor2; -} + destPath.setUsingNonZeroWinding (true); -bool RelativeCoordinate::operator!= (const RelativeCoordinate& other) const throw() -{ - return ! operator== (other); -} + const float maxMiterExtensionSquared = 9.0f * thickness * thickness; + const float width = 0.5f * thickness; -const RelativeCoordinate RelativeCoordinate::getAnchorCoordinate1() const -{ - return RelativeCoordinate (0.0, anchor1); -} + // Iterate the path, creating a list of the + // left/right-hand lines along either side of it... + PathFlatteningIterator it (*sourcePath, transform, 9.0f / extraAccuracy); -const RelativeCoordinate RelativeCoordinate::getAnchorCoordinate2() const -{ - return RelativeCoordinate (0.0, anchor2); -} + Array subPath; + subPath.ensureStorageAllocated (512); + LineSection l; + l.x1 = 0; + l.y1 = 0; -double RelativeCoordinate::resolveAnchor (const String& anchorName, const NamedCoordinateFinder* nameFinder, int recursionCounter) -{ - if (RelativeCoordinateHelpers::isOrigin (anchorName)) - return 0.0; + const float minSegmentLength = 2.0f / (extraAccuracy * extraAccuracy); - return RelativeCoordinateHelpers::findCoordinate (anchorName, nameFinder).resolve (nameFinder, recursionCounter + 1); -} + while (it.next()) + { + if (it.subPathIndex == 0) + { + if (subPath.size() > 0) + { + addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); + subPath.clearQuick(); + } -double RelativeCoordinate::resolve (const NamedCoordinateFinder* nameFinder, int recursionCounter) const -{ - if (recursionCounter > 150) - { - jassertfalse - throw RelativeCoordinateHelpers::RecursionException(); - } + l.x1 = it.x1; + l.y1 = it.y1; + } - const double pos1 = resolveAnchor (anchor1, nameFinder, recursionCounter); + l.x2 = it.x2; + l.y2 = it.y2; - return isProportional() ? pos1 + (resolveAnchor (anchor2, nameFinder, recursionCounter) - pos1) * value - : pos1 + value; -} + float dx = l.x2 - l.x1; + float dy = l.y2 - l.y1; -double RelativeCoordinate::resolve (const NamedCoordinateFinder* nameFinder) const -{ - try - { - return resolve (nameFinder, 0); - } - catch (RelativeCoordinateHelpers::RecursionException&) - {} + const float hypotSquared = dx*dx + dy*dy; - return 0.0; -} + if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath()) + { + const float len = std::sqrt (hypotSquared); -bool RelativeCoordinate::isRecursive (const NamedCoordinateFinder* nameFinder) const -{ - try - { - (void) resolve (nameFinder, 0); - } - catch (RelativeCoordinateHelpers::RecursionException&) - { - return true; - } + if (len == 0) + { + l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; + l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; + } + else + { + const float offset = width / len; + dx *= offset; + dy *= offset; - return false; -} + l.rx2 = l.x1 - dy; + l.ry2 = l.y1 + dx; + l.lx1 = l.x1 + dy; + l.ly1 = l.y1 - dx; -void RelativeCoordinate::moveToAbsolute (double newPos, const NamedCoordinateFinder* nameFinder) -{ - try - { - const double pos1 = resolveAnchor (anchor1, nameFinder, 0); + l.lx2 = l.x2 + dy; + l.ly2 = l.y2 - dx; + l.rx1 = l.x2 - dy; + l.ry1 = l.y2 + dx; + } - if (isProportional()) - { - const double size = resolveAnchor (anchor2, nameFinder, 0) - pos1; + subPath.add (l); - if (size != 0) - value = (newPos - pos1) / size; - } - else - { - value = newPos - pos1; + if (it.closesSubPath) + { + addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); + subPath.clearQuick(); + } + else + { + l.x1 = it.x2; + l.y1 = it.y2; + } + } } + + if (subPath.size() > 0) + addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); } - catch (RelativeCoordinateHelpers::RecursionException&) - {} } -void RelativeCoordinate::toggleProportionality (const NamedCoordinateFinder* nameFinder, - const String& proportionalAnchor1, const String& proportionalAnchor2) +void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath, + const AffineTransform& transform, const float extraAccuracy) const { - const double oldValue = resolve (nameFinder); - - anchor1 = proportionalAnchor1; - anchor2 = isProportional() ? String::empty : proportionalAnchor2; - - moveToAbsolute (oldValue, nameFinder); + PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath, + transform, extraAccuracy, 0); } -bool RelativeCoordinate::references (const String& coordName, const NamedCoordinateFinder* nameFinder) const +void PathStrokeType::createDashedStroke (Path& destPath, + const Path& sourcePath, + const float* dashLengths, + int numDashLengths, + const AffineTransform& transform, + const float extraAccuracy) const { - using namespace RelativeCoordinateHelpers; - - if (isOrigin (anchor1) && ! isProportional()) - return isOrigin (coordName); + if (thickness <= 0) + return; - return anchor1 == coordName - || anchor2 == coordName - || findCoordinate (anchor1, nameFinder).references (coordName, nameFinder) - || (isProportional() && findCoordinate (anchor2, nameFinder).references (coordName, nameFinder)); -} + // this should really be an even number.. + jassert ((numDashLengths & 1) == 0); -bool RelativeCoordinate::isDynamic() const -{ - return anchor2.isNotEmpty() || ! RelativeCoordinateHelpers::isOrigin (anchor1); -} + Path newDestPath; + PathFlatteningIterator it (sourcePath, transform, 9.0f / extraAccuracy); -const String RelativeCoordinate::toString() const -{ - using namespace RelativeCoordinateHelpers; + bool first = true; + int dashNum = 0; + float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f; + float dx = 0.0f, dy = 0.0f; - if (isProportional()) + for (;;) { - const String percent (limitedAccuracyString (value * 100.0)); + const bool isSolid = ((dashNum & 1) == 0); + const float dashLen = dashLengths [dashNum++ % numDashLengths]; - if (isOrigin (anchor1)) - { - if (anchor2 == Strings::parentRight || anchor2 == Strings::parentBottom) - return percent + "%"; - else - return percent + "% * " + anchor2; - } - else - return percent + "% * " + anchor1 + " -> " + anchor2; - } - else - { - if (isOrigin (anchor1)) - return limitedAccuracyString (value); - else if (value > 0) - return anchor1 + " + " + limitedAccuracyString (value); - else if (value < 0) - return anchor1 + " - " + limitedAccuracyString (-value); - else - return anchor1; - } -} + jassert (dashLen > 0); // must be a positive increment! + if (dashLen <= 0) + break; -const double RelativeCoordinate::getEditableNumber() const -{ - return isProportional() ? value * 100.0 : value; -} + pos += dashLen; + + while (pos > lineEndPos) + { + if (! it.next()) + { + if (isSolid && ! first) + newDestPath.lineTo (it.x2, it.y2); -void RelativeCoordinate::setEditableNumber (const double newValue) -{ - value = isProportional() ? newValue / 100.0 : newValue; -} + createStrokedPath (destPath, newDestPath, AffineTransform::identity, extraAccuracy); + return; + } -const String RelativeCoordinate::getAnchorName1 (const String& returnValueIfOrigin) const -{ - return RelativeCoordinateHelpers::isOrigin (anchor1) ? returnValueIfOrigin : anchor1; -} + if (isSolid && ! first) + newDestPath.lineTo (it.x1, it.y1); + else + newDestPath.startNewSubPath (it.x1, it.y1); -const String RelativeCoordinate::getAnchorName2 (const String& returnValueIfOrigin) const -{ - return RelativeCoordinateHelpers::isOrigin (anchor2) ? returnValueIfOrigin : anchor2; -} + dx = it.x2 - it.x1; + dy = it.y2 - it.y1; + lineLen = juce_hypotf (dx, dy); + lineEndPos += lineLen; + first = it.closesSubPath; + } -void RelativeCoordinate::changeAnchor1 (const String& newAnchorName, const NamedCoordinateFinder* nameFinder) -{ - jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_.")); + const float alpha = (pos - (lineEndPos - lineLen)) / lineLen; - const double oldValue = resolve (nameFinder); - anchor1 = RelativeCoordinateHelpers::isOrigin (newAnchorName) ? String::empty : newAnchorName; - moveToAbsolute (oldValue, nameFinder); + if (isSolid) + newDestPath.lineTo (it.x1 + dx * alpha, + it.y1 + dy * alpha); + else + newDestPath.startNewSubPath (it.x1 + dx * alpha, + it.y1 + dy * alpha); + } } -void RelativeCoordinate::changeAnchor2 (const String& newAnchorName, const NamedCoordinateFinder* nameFinder) +void PathStrokeType::createStrokeWithArrowheads (Path& destPath, + const Path& sourcePath, + const float arrowheadStartWidth, const float arrowheadStartLength, + const float arrowheadEndWidth, const float arrowheadEndLength, + const AffineTransform& transform, + const float extraAccuracy) const { - jassert (isProportional()); - jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_.")); + PathStrokeHelpers::Arrowhead head; + head.startWidth = arrowheadStartWidth; + head.startLength = arrowheadStartLength; + head.endWidth = arrowheadEndWidth; + head.endLength = arrowheadEndLength; - const double oldValue = resolve (nameFinder); - anchor2 = RelativeCoordinateHelpers::isOrigin (newAnchorName) ? String::empty : newAnchorName; - moveToAbsolute (oldValue, nameFinder); + PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, + destPath, sourcePath, transform, extraAccuracy, &head); } -void RelativeCoordinate::renameAnchorIfUsed (const String& oldName, const String& newName, const NamedCoordinateFinder* nameFinder) -{ - using namespace RelativeCoordinateHelpers; - jassert (oldName.isNotEmpty()); - jassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); +END_JUCE_NAMESPACE +/*** End of inlined file: juce_PathStrokeType.cpp ***/ - if (newName.isEmpty()) - { - if (getObjectName (anchor1) == oldName - || getObjectName (anchor2) == oldName) - { - value = resolve (nameFinder); - anchor1 = String::empty; - anchor2 = String::empty; - } - } - else - { - if (getObjectName (anchor1) == oldName) - anchor1 = newName + "." + getEdgeName (anchor1); - if (getObjectName (anchor2) == oldName) - anchor2 = newName + "." + getEdgeName (anchor2); - } -} +/*** Start of inlined file: juce_PositionedRectangle.cpp ***/ +BEGIN_JUCE_NAMESPACE -RelativePoint::RelativePoint() +PositionedRectangle::PositionedRectangle() throw() + : x (0.0), + y (0.0), + w (0.0), + h (0.0), + xMode (anchorAtLeftOrTop | absoluteFromParentTopLeft), + yMode (anchorAtLeftOrTop | absoluteFromParentTopLeft), + wMode (absoluteSize), + hMode (absoluteSize) { } -RelativePoint::RelativePoint (const Point& absolutePoint) - : x (absolutePoint.getX()), y (absolutePoint.getY()) +PositionedRectangle::PositionedRectangle (const PositionedRectangle& other) throw() + : x (other.x), + y (other.y), + w (other.w), + h (other.h), + xMode (other.xMode), + yMode (other.yMode), + wMode (other.wMode), + hMode (other.hMode) { } -RelativePoint::RelativePoint (const float x_, const float y_) - : x (x_), y (y_) +PositionedRectangle& PositionedRectangle::operator= (const PositionedRectangle& other) throw() { -} + x = other.x; + y = other.y; + w = other.w; + h = other.h; + xMode = other.xMode; + yMode = other.yMode; + wMode = other.wMode; + hMode = other.hMode; -RelativePoint::RelativePoint (const RelativeCoordinate& x_, const RelativeCoordinate& y_) - : x (x_), y (y_) -{ + return *this; } -RelativePoint::RelativePoint (const String& s) +PositionedRectangle::~PositionedRectangle() throw() { - int i = 0; - x = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); - RelativeCoordinateHelpers::skipComma (s, i); - y = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); } -bool RelativePoint::operator== (const RelativePoint& other) const throw() +bool PositionedRectangle::operator== (const PositionedRectangle& other) const throw() { - return x == other.x && y == other.y; + return x == other.x + && y == other.y + && w == other.w + && h == other.h + && xMode == other.xMode + && yMode == other.yMode + && wMode == other.wMode + && hMode == other.hMode; } -bool RelativePoint::operator!= (const RelativePoint& other) const throw() +bool PositionedRectangle::operator!= (const PositionedRectangle& other) const throw() { return ! operator== (other); } -const Point RelativePoint::resolve (const RelativeCoordinate::NamedCoordinateFinder* nameFinder) const +PositionedRectangle::PositionedRectangle (const String& stringVersion) throw() { - return Point ((float) x.resolve (nameFinder), - (float) y.resolve (nameFinder)); -} + StringArray tokens; + tokens.addTokens (stringVersion, false); -void RelativePoint::moveToAbsolute (const Point& newPos, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) -{ - x.moveToAbsolute (newPos.getX(), nameFinder); - y.moveToAbsolute (newPos.getY(), nameFinder); + decodePosString (tokens [0], xMode, x); + decodePosString (tokens [1], yMode, y); + decodeSizeString (tokens [2], wMode, w); + decodeSizeString (tokens [3], hMode, h); } -const String RelativePoint::toString() const +const String PositionedRectangle::toString() const throw() { - return x.toString() + ", " + y.toString(); -} + String s; + s.preallocateStorage (12); -void RelativePoint::renameAnchorIfUsed (const String& oldName, const String& newName, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) -{ - x.renameAnchorIfUsed (oldName, newName, nameFinder); - y.renameAnchorIfUsed (oldName, newName, nameFinder); -} + addPosDescription (s, xMode, x); + s << ' '; + addPosDescription (s, yMode, y); + s << ' '; + addSizeDescription (s, wMode, w); + s << ' '; + addSizeDescription (s, hMode, h); -bool RelativePoint::isDynamic() const -{ - return x.isDynamic() || y.isDynamic(); + return s; } -RelativeRectangle::RelativeRectangle() +const Rectangle PositionedRectangle::getRectangle (const Rectangle& target) const throw() { -} + jassert (! target.isEmpty()); -RelativeRectangle::RelativeRectangle (const RelativeCoordinate& left_, const RelativeCoordinate& right_, - const RelativeCoordinate& top_, const RelativeCoordinate& bottom_) - : left (left_), right (right_), top (top_), bottom (bottom_) -{ + double x_, y_, w_, h_; + applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth()); + applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight()); + + return Rectangle (roundToInt (x_), roundToInt (y_), + roundToInt (w_), roundToInt (h_)); } -RelativeRectangle::RelativeRectangle (const Rectangle& rect, const String& componentName) - : left (rect.getX()), - right (rect.getWidth(), componentName + "." + RelativeCoordinate::Strings::left), - top (rect.getY()), - bottom (rect.getHeight(), componentName + "." + RelativeCoordinate::Strings::top) +void PositionedRectangle::getRectangleDouble (const Rectangle& target, + double& x_, double& y_, + double& w_, double& h_) const throw() { + jassert (! target.isEmpty()); + + applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth()); + applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight()); } -RelativeRectangle::RelativeRectangle (const String& s) +void PositionedRectangle::applyToComponent (Component& comp) const throw() { - int i = 0; - left = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); - RelativeCoordinateHelpers::skipComma (s, i); - top = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); - RelativeCoordinateHelpers::skipComma (s, i); - right = RelativeCoordinateHelpers::readNextCoordinate (s, i, true); - RelativeCoordinateHelpers::skipComma (s, i); - bottom = RelativeCoordinateHelpers::readNextCoordinate (s, i, false); + comp.setBounds (getRectangle (Rectangle (comp.getParentWidth(), comp.getParentHeight()))); } -bool RelativeRectangle::operator== (const RelativeRectangle& other) const throw() +void PositionedRectangle::updateFrom (const Rectangle& rectangle, + const Rectangle& target) throw() { - return left == other.left && top == other.top && right == other.right && bottom == other.bottom; + updatePosAndSize (x, w, rectangle.getX(), rectangle.getWidth(), xMode, wMode, target.getX(), target.getWidth()); + updatePosAndSize (y, h, rectangle.getY(), rectangle.getHeight(), yMode, hMode, target.getY(), target.getHeight()); } -bool RelativeRectangle::operator!= (const RelativeRectangle& other) const throw() +void PositionedRectangle::updateFromDouble (const double newX, const double newY, + const double newW, const double newH, + const Rectangle& target) throw() { - return ! operator== (other); + updatePosAndSize (x, w, newX, newW, xMode, wMode, target.getX(), target.getWidth()); + updatePosAndSize (y, h, newY, newH, yMode, hMode, target.getY(), target.getHeight()); } -const Rectangle RelativeRectangle::resolve (const RelativeCoordinate::NamedCoordinateFinder* nameFinder) const +void PositionedRectangle::updateFromComponent (const Component& comp) throw() { - const double l = left.resolve (nameFinder); - const double r = right.resolve (nameFinder); - const double t = top.resolve (nameFinder); - const double b = bottom.resolve (nameFinder); - - return Rectangle ((float) l, (float) t, (float) (r - l), (float) (b - t)); + if (comp.getParentComponent() == 0 && ! comp.isOnDesktop()) + updateFrom (comp.getBounds(), Rectangle()); + else + updateFrom (comp.getBounds(), Rectangle (comp.getParentWidth(), comp.getParentHeight())); } -void RelativeRectangle::moveToAbsolute (const Rectangle& newPos, const RelativeCoordinate::NamedCoordinateFinder* nameFinder) +PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointX() const throw() { - left.moveToAbsolute (newPos.getX(), nameFinder); - right.moveToAbsolute (newPos.getRight(), nameFinder); - top.moveToAbsolute (newPos.getY(), nameFinder); - bottom.moveToAbsolute (newPos.getBottom(), nameFinder); + return (AnchorPoint) (xMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre)); } -const String RelativeRectangle::toString() const +PositionedRectangle::PositionMode PositionedRectangle::getPositionModeX() const throw() { - return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString(); + return (PositionMode) (xMode & (absoluteFromParentTopLeft + | absoluteFromParentBottomRight + | absoluteFromParentCentre + | proportionOfParentSize)); } -void RelativeRectangle::renameAnchorIfUsed (const String& oldName, const String& newName, - const RelativeCoordinate::NamedCoordinateFinder* nameFinder) +PositionedRectangle::AnchorPoint PositionedRectangle::getAnchorPointY() const throw() { - left.renameAnchorIfUsed (oldName, newName, nameFinder); - right.renameAnchorIfUsed (oldName, newName, nameFinder); - top.renameAnchorIfUsed (oldName, newName, nameFinder); - bottom.renameAnchorIfUsed (oldName, newName, nameFinder); + return (AnchorPoint) (yMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre)); } -RelativePointPath::RelativePointPath() - : usesNonZeroWinding (true), - containsDynamicPoints (false) +PositionedRectangle::PositionMode PositionedRectangle::getPositionModeY() const throw() { + return (PositionMode) (yMode & (absoluteFromParentTopLeft + | absoluteFromParentBottomRight + | absoluteFromParentCentre + | proportionOfParentSize)); } -RelativePointPath::RelativePointPath (const RelativePointPath& other) - : usesNonZeroWinding (true), - containsDynamicPoints (false) +PositionedRectangle::SizeMode PositionedRectangle::getWidthMode() const throw() { - ValueTree state (DrawablePath::valueTreeType); - other.writeTo (state, 0); - parse (state); + return (SizeMode) wMode; } -RelativePointPath::RelativePointPath (const ValueTree& drawable) - : usesNonZeroWinding (true), - containsDynamicPoints (false) +PositionedRectangle::SizeMode PositionedRectangle::getHeightMode() const throw() { - parse (drawable); + return (SizeMode) hMode; } -RelativePointPath::RelativePointPath (const Path& path) +void PositionedRectangle::setModes (const AnchorPoint xAnchor, + const PositionMode xMode_, + const AnchorPoint yAnchor, + const PositionMode yMode_, + const SizeMode widthMode, + const SizeMode heightMode, + const Rectangle& target) throw() { - usesNonZeroWinding = path.isUsingNonZeroWinding(); + if (xMode != (xAnchor | xMode_) || wMode != widthMode) + { + double tx, tw; + applyPosAndSize (tx, tw, x, w, xMode, wMode, target.getX(), target.getWidth()); - Path::Iterator i (path); + xMode = (uint8) (xAnchor | xMode_); + wMode = (uint8) widthMode; - while (i.next()) - { - switch (i.elementType) - { - case Path::Iterator::startNewSubPath: elements.add (new StartSubPath (RelativePoint (i.x1, i.y1))); break; - case Path::Iterator::lineTo: elements.add (new LineTo (RelativePoint (i.x1, i.y1))); break; - case Path::Iterator::quadraticTo: elements.add (new QuadraticTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2))); break; - case Path::Iterator::cubicTo: elements.add (new CubicTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2), RelativePoint (i.x3, i.y3))); break; - case Path::Iterator::closePath: elements.add (new CloseSubPath()); break; - default: jassertfalse; break; - } + updatePosAndSize (x, w, tx, tw, xMode, wMode, target.getX(), target.getWidth()); } -} -void RelativePointPath::writeTo (ValueTree state, UndoManager* undoManager) const -{ - DrawablePath::ValueTreeWrapper wrapper (state); - wrapper.setUsesNonZeroWinding (usesNonZeroWinding, undoManager); + if (yMode != (yAnchor | yMode_) || hMode != heightMode) + { + double ty, th; + applyPosAndSize (ty, th, y, h, yMode, hMode, target.getY(), target.getHeight()); - ValueTree pathTree (wrapper.getPathState()); - pathTree.removeAllChildren (undoManager); + yMode = (uint8) (yAnchor | yMode_); + hMode = (uint8) heightMode; - for (int i = 0; i < elements.size(); ++i) - pathTree.addChild (elements.getUnchecked(i)->createTree(), -1, undoManager); + updatePosAndSize (y, h, ty, th, yMode, hMode, target.getY(), target.getHeight()); + } } -void RelativePointPath::parse (const ValueTree& state) +bool PositionedRectangle::isPositionAbsolute() const throw() { - DrawablePath::ValueTreeWrapper wrapper (state); - usesNonZeroWinding = wrapper.usesNonZeroWinding(); - RelativePoint points[3]; + return xMode == absoluteFromParentTopLeft + && yMode == absoluteFromParentTopLeft + && wMode == absoluteSize + && hMode == absoluteSize; +} - const ValueTree pathTree (wrapper.getPathState()); - const int num = pathTree.getNumChildren(); - for (int i = 0; i < num; ++i) +void PositionedRectangle::addPosDescription (String& s, const uint8 mode, const double value) const throw() +{ + if ((mode & proportionOfParentSize) != 0) { - const DrawablePath::ValueTreeWrapper::Element e (pathTree.getChild(i)); - - const int numCps = e.getNumControlPoints(); - for (int j = 0; j < numCps; ++j) - { - points[j] = e.getControlPoint (j); - containsDynamicPoints = containsDynamicPoints || points[j].isDynamic(); - } - - const Identifier type (e.getType()); + s << (roundToInt (value * 100000.0) / 1000.0) << '%'; + } + else + { + s << (roundToInt (value * 100.0) / 100.0); - if (type == DrawablePath::ValueTreeWrapper::Element::startSubPathElement) - elements.add (new StartSubPath (points[0])); - else if (type == DrawablePath::ValueTreeWrapper::Element::closeSubPathElement) - elements.add (new CloseSubPath()); - else if (type == DrawablePath::ValueTreeWrapper::Element::lineToElement) - elements.add (new LineTo (points[0])); - else if (type == DrawablePath::ValueTreeWrapper::Element::quadraticToElement) - elements.add (new QuadraticTo (points[0], points[1])); - else if (type == DrawablePath::ValueTreeWrapper::Element::cubicToElement) - elements.add (new CubicTo (points[0], points[1], points[2])); - else - jassertfalse; + if ((mode & absoluteFromParentBottomRight) != 0) + s << 'R'; + else if ((mode & absoluteFromParentCentre) != 0) + s << 'C'; } -} -RelativePointPath::~RelativePointPath() -{ + if ((mode & anchorAtRightOrBottom) != 0) + s << 'r'; + else if ((mode & anchorAtCentre) != 0) + s << 'c'; } -void RelativePointPath::swapWith (RelativePointPath& other) throw() +void PositionedRectangle::addSizeDescription (String& s, const uint8 mode, const double value) const throw() { - elements.swapWithArray (other.elements); - swapVariables (usesNonZeroWinding, other.usesNonZeroWinding); + if (mode == proportionalSize) + s << (roundToInt (value * 100000.0) / 1000.0) << '%'; + else if (mode == parentSizeMinusAbsolute) + s << (roundToInt (value * 100.0) / 100.0) << 'M'; + else + s << (roundToInt (value * 100.0) / 100.0); } -void RelativePointPath::createPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) +void PositionedRectangle::decodePosString (const String& s, uint8& mode, double& value) throw() { - for (int i = 0; i < elements.size(); ++i) - elements.getUnchecked(i)->addToPath (path, coordFinder); -} + if (s.containsChar ('r')) + mode = anchorAtRightOrBottom; + else if (s.containsChar ('c')) + mode = anchorAtCentre; + else + mode = anchorAtLeftOrTop; -bool RelativePointPath::containsAnyDynamicPoints() const -{ - return containsDynamicPoints; + if (s.containsChar ('%')) + { + mode |= proportionOfParentSize; + value = s.removeCharacters ("%rcRC").getDoubleValue() / 100.0; + } + else + { + if (s.containsChar ('R')) + mode |= absoluteFromParentBottomRight; + else if (s.containsChar ('C')) + mode |= absoluteFromParentCentre; + else + mode |= absoluteFromParentTopLeft; + + value = s.removeCharacters ("rcRC").getDoubleValue(); + } } -RelativePointPath::ElementBase::ElementBase (const ElementType type_) : type (type_) +void PositionedRectangle::decodeSizeString (const String& s, uint8& mode, double& value) throw() { + if (s.containsChar ('%')) + { + mode = proportionalSize; + value = s.upToFirstOccurrenceOf ("%", false, false).getDoubleValue() / 100.0; + } + else if (s.containsChar ('M')) + { + mode = parentSizeMinusAbsolute; + value = s.getDoubleValue(); + } + else + { + mode = absoluteSize; + value = s.getDoubleValue(); + } } -RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos) - : ElementBase (startSubPathElement), startPos (pos) +void PositionedRectangle::applyPosAndSize (double& xOut, double& wOut, + const double x_, const double w_, + const uint8 xMode_, const uint8 wMode_, + const int parentPos, + const int parentSize) const throw() { + if (wMode_ == proportionalSize) + wOut = roundToInt (w_ * parentSize); + else if (wMode_ == parentSizeMinusAbsolute) + wOut = jmax (0, parentSize - roundToInt (w_)); + else + wOut = roundToInt (w_); + + if ((xMode_ & proportionOfParentSize) != 0) + xOut = parentPos + x_ * parentSize; + else if ((xMode_ & absoluteFromParentBottomRight) != 0) + xOut = (parentPos + parentSize) - x_; + else if ((xMode_ & absoluteFromParentCentre) != 0) + xOut = x_ + (parentPos + parentSize / 2); + else + xOut = x_ + parentPos; + + if ((xMode_ & anchorAtRightOrBottom) != 0) + xOut -= wOut; + else if ((xMode_ & anchorAtCentre) != 0) + xOut -= wOut / 2; } -const ValueTree RelativePointPath::StartSubPath::createTree() const +void PositionedRectangle::updatePosAndSize (double& xOut, double& wOut, + double x_, const double w_, + const uint8 xMode_, const uint8 wMode_, + const int parentPos, + const int parentSize) const throw() { - ValueTree v (DrawablePath::ValueTreeWrapper::Element::startSubPathElement); - v.setProperty (DrawablePath::ValueTreeWrapper::point1, startPos.toString(), 0); - return v; + if (wMode_ == proportionalSize) + { + if (parentSize > 0) + wOut = w_ / parentSize; + } + else if (wMode_ == parentSizeMinusAbsolute) + wOut = parentSize - w_; + else + wOut = w_; + + if ((xMode_ & anchorAtRightOrBottom) != 0) + x_ += w_; + else if ((xMode_ & anchorAtCentre) != 0) + x_ += w_ / 2; + + if ((xMode_ & proportionOfParentSize) != 0) + { + if (parentSize > 0) + xOut = (x_ - parentPos) / parentSize; + } + else if ((xMode_ & absoluteFromParentBottomRight) != 0) + xOut = (parentPos + parentSize) - x_; + else if ((xMode_ & absoluteFromParentCentre) != 0) + xOut = x_ - (parentPos + parentSize / 2); + else + xOut = x_ - parentPos; } -void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +END_JUCE_NAMESPACE +/*** End of inlined file: juce_PositionedRectangle.cpp ***/ + + +/*** Start of inlined file: juce_RectangleList.cpp ***/ +BEGIN_JUCE_NAMESPACE + +RectangleList::RectangleList() throw() { - path.startNewSubPath (startPos.resolve (coordFinder)); } -RelativePoint* RelativePointPath::StartSubPath::getControlPoints (int& numPoints) +RectangleList::RectangleList (const Rectangle& rect) { - numPoints = 1; - return &startPos; + if (! rect.isEmpty()) + rects.add (rect); } -RelativePointPath::CloseSubPath::CloseSubPath() - : ElementBase (closeSubPathElement) +RectangleList::RectangleList (const RectangleList& other) + : rects (other.rects) { } -const ValueTree RelativePointPath::CloseSubPath::createTree() const +RectangleList& RectangleList::operator= (const RectangleList& other) { - return ValueTree (DrawablePath::ValueTreeWrapper::Element::closeSubPathElement); + rects = other.rects; + return *this; } -void RelativePointPath::CloseSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder*) const +RectangleList::~RectangleList() { - path.closeSubPath(); } -RelativePoint* RelativePointPath::CloseSubPath::getControlPoints (int& numPoints) +void RectangleList::clear() { - numPoints = 0; - return 0; + rects.clearQuick(); } -RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_) - : ElementBase (lineToElement), endPoint (endPoint_) +const Rectangle RectangleList::getRectangle (const int index) const throw() { + if (((unsigned int) index) < (unsigned int) rects.size()) + return rects.getReference (index); + + return Rectangle(); } -const ValueTree RelativePointPath::LineTo::createTree() const +bool RectangleList::isEmpty() const throw() { - ValueTree v (DrawablePath::ValueTreeWrapper::Element::lineToElement); - v.setProperty (DrawablePath::ValueTreeWrapper::point1, endPoint.toString(), 0); - return v; + return rects.size() == 0; } -void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +RectangleList::Iterator::Iterator (const RectangleList& list) throw() + : current (0), + owner (list), + index (list.rects.size()) { - path.lineTo (endPoint.resolve (coordFinder)); } -RelativePoint* RelativePointPath::LineTo::getControlPoints (int& numPoints) +RectangleList::Iterator::~Iterator() { - numPoints = 1; - return &endPoint; } -RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, const RelativePoint& endPoint) - : ElementBase (quadraticToElement) +bool RectangleList::Iterator::next() throw() { - controlPoints[0] = controlPoint; - controlPoints[1] = endPoint; + if (--index >= 0) + { + current = & (owner.rects.getReference (index)); + return true; + } + + return false; } -const ValueTree RelativePointPath::QuadraticTo::createTree() const +void RectangleList::add (const Rectangle& rect) { - ValueTree v (DrawablePath::ValueTreeWrapper::Element::quadraticToElement); - v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); - v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); - return v; + if (! rect.isEmpty()) + { + if (rects.size() == 0) + { + rects.add (rect); + } + else + { + bool anyOverlaps = false; + + int i; + for (i = rects.size(); --i >= 0;) + { + Rectangle& ourRect = rects.getReference (i); + + if (rect.intersects (ourRect)) + { + if (rect.contains (ourRect)) + rects.remove (i); + else if (! ourRect.reduceIfPartlyContainedIn (rect)) + anyOverlaps = true; + } + } + + if (anyOverlaps && rects.size() > 0) + { + RectangleList r (rect); + + for (i = rects.size(); --i >= 0;) + { + const Rectangle& ourRect = rects.getReference (i); + + if (rect.intersects (ourRect)) + { + r.subtract (ourRect); + + if (r.rects.size() == 0) + return; + } + } + + for (i = r.getNumRectangles(); --i >= 0;) + rects.add (r.rects.getReference (i)); + } + else + { + rects.add (rect); + } + } + } } -void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +void RectangleList::addWithoutMerging (const Rectangle& rect) { - path.quadraticTo (controlPoints[0].resolve (coordFinder), - controlPoints[1].resolve (coordFinder)); + if (! rect.isEmpty()) + rects.add (rect); } -RelativePoint* RelativePointPath::QuadraticTo::getControlPoints (int& numPoints) +void RectangleList::add (const int x, const int y, const int w, const int h) { - numPoints = 2; - return controlPoints; + if (rects.size() == 0) + { + if (w > 0 && h > 0) + rects.add (Rectangle (x, y, w, h)); + } + else + { + add (Rectangle (x, y, w, h)); + } } -RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const RelativePoint& controlPoint2, const RelativePoint& endPoint) - : ElementBase (cubicToElement) +void RectangleList::add (const RectangleList& other) { - controlPoints[0] = controlPoint1; - controlPoints[1] = controlPoint2; - controlPoints[2] = endPoint; + for (int i = 0; i < other.rects.size(); ++i) + add (other.rects.getReference (i)); } -const ValueTree RelativePointPath::CubicTo::createTree() const +void RectangleList::subtract (const Rectangle& rect) { - ValueTree v (DrawablePath::ValueTreeWrapper::Element::cubicToElement); - v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), 0); - v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), 0); - v.setProperty (DrawablePath::ValueTreeWrapper::point3, controlPoints[2].toString(), 0); - return v; + const int originalNumRects = rects.size(); + + if (originalNumRects > 0) + { + const int x1 = rect.x; + const int y1 = rect.y; + const int x2 = x1 + rect.w; + const int y2 = y1 + rect.h; + + for (int i = getNumRectangles(); --i >= 0;) + { + Rectangle& r = rects.getReference (i); + + const int rx1 = r.x; + const int ry1 = r.y; + const int rx2 = rx1 + r.w; + const int ry2 = ry1 + r.h; + + if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2)) + { + if (x1 > rx1 && x1 < rx2) + { + if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2) + { + r.w = x1 - rx1; + } + else + { + r.x = x1; + r.w = rx2 - x1; + + rects.insert (i + 1, Rectangle (rx1, ry1, x1 - rx1, ry2 - ry1)); + i += 2; + } + } + else if (x2 > rx1 && x2 < rx2) + { + r.x = x2; + r.w = rx2 - x2; + + if (y1 > ry1 || y2 < ry2 || x1 > rx1) + { + rects.insert (i + 1, Rectangle (rx1, ry1, x2 - rx1, ry2 - ry1)); + i += 2; + } + } + else if (y1 > ry1 && y1 < ry2) + { + if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2) + { + r.h = y1 - ry1; + } + else + { + r.y = y1; + r.h = ry2 - y1; + + rects.insert (i + 1, Rectangle (rx1, ry1, rx2 - rx1, y1 - ry1)); + i += 2; + } + } + else if (y2 > ry1 && y2 < ry2) + { + r.y = y2; + r.h = ry2 - y2; + + if (x1 > rx1 || x2 < rx2 || y1 > ry1) + { + rects.insert (i + 1, Rectangle (rx1, ry1, rx2 - rx1, y2 - ry1)); + i += 2; + } + } + else + { + rects.remove (i); + } + } + } + } } -void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const +bool RectangleList::subtract (const RectangleList& otherList) { - path.cubicTo (controlPoints[0].resolve (coordFinder), - controlPoints[1].resolve (coordFinder), - controlPoints[2].resolve (coordFinder)); + for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;) + subtract (otherList.rects.getReference (i)); + + return rects.size() > 0; } -RelativePoint* RelativePointPath::CubicTo::getControlPoints (int& numPoints) +bool RectangleList::clipTo (const Rectangle& rect) { - numPoints = 3; - return controlPoints; + bool notEmpty = false; + + if (rect.isEmpty()) + { + clear(); + } + else + { + for (int i = rects.size(); --i >= 0;) + { + Rectangle& r = rects.getReference (i); + + if (! rect.intersectRectangle (r.x, r.y, r.w, r.h)) + rects.remove (i); + else + notEmpty = true; + } + } + + return notEmpty; } -RelativeParallelogram::RelativeParallelogram() +bool RectangleList::clipTo (const RectangleList& other) { + if (rects.size() == 0) + return false; + + RectangleList result; + + for (int j = 0; j < rects.size(); ++j) + { + const Rectangle& rect = rects.getReference (j); + + for (int i = other.rects.size(); --i >= 0;) + { + Rectangle r (other.rects.getReference (i)); + + if (rect.intersectRectangle (r.x, r.y, r.w, r.h)) + result.rects.add (r); + } + } + + swapWith (result); + + return ! isEmpty(); } -RelativeParallelogram::RelativeParallelogram (const RelativePoint& topLeft_, const RelativePoint& topRight_, const RelativePoint& bottomLeft_) - : topLeft (topLeft_), topRight (topRight_), bottomLeft (bottomLeft_) +bool RectangleList::getIntersectionWith (const Rectangle& rect, RectangleList& destRegion) const { + destRegion.clear(); + + if (! rect.isEmpty()) + { + for (int i = rects.size(); --i >= 0;) + { + Rectangle r (rects.getReference (i)); + + if (rect.intersectRectangle (r.x, r.y, r.w, r.h)) + destRegion.rects.add (r); + } + } + + return destRegion.rects.size() > 0; } -RelativeParallelogram::RelativeParallelogram (const String& topLeft_, const String& topRight_, const String& bottomLeft_) - : topLeft (topLeft_), topRight (topRight_), bottomLeft (bottomLeft_) +void RectangleList::swapWith (RectangleList& otherList) throw() { + rects.swapWithArray (otherList.rects); } -RelativeParallelogram::~RelativeParallelogram() +void RectangleList::consolidate() { + int i; + for (i = 0; i < getNumRectangles() - 1; ++i) + { + Rectangle& r = rects.getReference (i); + const int rx1 = r.x; + const int ry1 = r.y; + const int rx2 = rx1 + r.w; + const int ry2 = ry1 + r.h; + + for (int j = rects.size(); --j > i;) + { + Rectangle& r2 = rects.getReference (j); + const int jrx1 = r2.x; + const int jry1 = r2.y; + const int jrx2 = jrx1 + r2.w; + const int jry2 = jry1 + r2.h; + + // if the vertical edges of any blocks are touching and their horizontals don't + // line up, split them horizontally.. + if (jrx1 == rx2 || jrx2 == rx1) + { + if (jry1 > ry1 && jry1 < ry2) + { + r.h = jry1 - ry1; + rects.add (Rectangle (rx1, jry1, rx2 - rx1, ry2 - jry1)); + i = -1; + break; + } + + if (jry2 > ry1 && jry2 < ry2) + { + r.h = jry2 - ry1; + rects.add (Rectangle (rx1, jry2, rx2 - rx1, ry2 - jry2)); + i = -1; + break; + } + else if (ry1 > jry1 && ry1 < jry2) + { + r2.h = ry1 - jry1; + rects.add (Rectangle (jrx1, ry1, jrx2 - jrx1, jry2 - ry1)); + i = -1; + break; + } + else if (ry2 > jry1 && ry2 < jry2) + { + r2.h = ry2 - jry1; + rects.add (Rectangle (jrx1, ry2, jrx2 - jrx1, jry2 - ry2)); + i = -1; + break; + } + } + } + } + + for (i = 0; i < rects.size() - 1; ++i) + { + Rectangle& r = rects.getReference (i); + + for (int j = rects.size(); --j > i;) + { + if (r.enlargeIfAdjacent (rects.getReference (j))) + { + rects.remove (j); + i = -1; + break; + } + } + } } -void RelativeParallelogram::resolveThreePoints (Point* points, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +bool RectangleList::containsPoint (const int x, const int y) const throw() { - points[0] = topLeft.resolve (coordFinder); - points[1] = topRight.resolve (coordFinder); - points[2] = bottomLeft.resolve (coordFinder); + for (int i = getNumRectangles(); --i >= 0;) + if (rects.getReference (i).contains (x, y)) + return true; + + return false; } -void RelativeParallelogram::resolveFourCorners (Point* points, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +bool RectangleList::containsRectangle (const Rectangle& rectangleToCheck) const { - resolveThreePoints (points, coordFinder); - points[3] = points[1] + (points[2] - points[0]); + if (rects.size() > 1) + { + RectangleList r (rectangleToCheck); + + for (int i = rects.size(); --i >= 0;) + { + r.subtract (rects.getReference (i)); + + if (r.rects.size() == 0) + return true; + } + } + else if (rects.size() > 0) + { + return rects.getReference (0).contains (rectangleToCheck); + } + + return false; } -const Rectangle RelativeParallelogram::getBounds (RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +bool RectangleList::intersectsRectangle (const Rectangle& rectangleToCheck) const throw() { - Point points[4]; - resolveFourCorners (points, coordFinder); - return Rectangle::findAreaContainingPoints (points, 4); + for (int i = rects.size(); --i >= 0;) + if (rects.getReference (i).intersects (rectangleToCheck)) + return true; + + return false; } -void RelativeParallelogram::getPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* const coordFinder) const +bool RectangleList::intersects (const RectangleList& other) const throw() { - Point points[4]; - resolveFourCorners (points, coordFinder); + for (int i = rects.size(); --i >= 0;) + if (other.intersectsRectangle (rects.getReference (i))) + return true; - path.startNewSubPath (points[0]); - path.lineTo (points[1]); - path.lineTo (points[3]); - path.lineTo (points[2]); - path.closeSubPath(); + return false; } -const AffineTransform RelativeParallelogram::resetToPerpendicular (RelativeCoordinate::NamedCoordinateFinder* const coordFinder) +const Rectangle RectangleList::getBounds() const throw() { - Point corners[3]; - resolveThreePoints (corners, coordFinder); + if (rects.size() <= 1) + { + if (rects.size() == 0) + return Rectangle(); + else + return rects.getReference (0); + } + else + { + const Rectangle& r = rects.getReference (0); - const Line top (corners[0], corners[1]); - const Line left (corners[0], corners[2]); - const Point newTopRight (corners[0] + Point (top.getLength(), 0.0f)); - const Point newBottomLeft (corners[0] + Point (0.0f, left.getLength())); + int minX = r.x; + int minY = r.y; + int maxX = minX + r.w; + int maxY = minY + r.h; - topRight.moveToAbsolute (newTopRight, coordFinder); - bottomLeft.moveToAbsolute (newBottomLeft, coordFinder); + for (int i = rects.size(); --i > 0;) + { + const Rectangle& r2 = rects.getReference (i); - return AffineTransform::fromTargetPoints (corners[0].getX(), corners[0].getY(), corners[0].getX(), corners[0].getY(), - corners[1].getX(), corners[1].getY(), newTopRight.getX(), newTopRight.getY(), - corners[2].getX(), corners[2].getY(), newBottomLeft.getX(), newBottomLeft.getY()); -} + minX = jmin (minX, r2.x); + minY = jmin (minY, r2.y); + maxX = jmax (maxX, r2.getRight()); + maxY = jmax (maxY, r2.getBottom()); + } -bool RelativeParallelogram::operator== (const RelativeParallelogram& other) const throw() -{ - return topLeft == other.topLeft && topRight == other.topRight && bottomLeft == other.bottomLeft; + return Rectangle (minX, minY, maxX - minX, maxY - minY); + } } -bool RelativeParallelogram::operator!= (const RelativeParallelogram& other) const throw() +void RectangleList::offsetAll (const int dx, const int dy) throw() { - return ! operator== (other); + for (int i = rects.size(); --i >= 0;) + { + Rectangle& r = rects.getReference (i); + + r.x += dx; + r.y += dy; + } } -const Point RelativeParallelogram::getInternalCoordForPoint (const Point* const corners, Point target) throw() +const Path RectangleList::toPath() const { - const Point tr (corners[1] - corners[0]); - const Point bl (corners[2] - corners[0]); - target -= corners[0]; + Path p; - return Point (Line (Point(), tr).getIntersection (Line (target, target - bl)).getDistanceFromOrigin(), - Line (Point(), bl).getIntersection (Line (target, target - tr)).getDistanceFromOrigin()); -} + for (int i = rects.size(); --i >= 0;) + { + const Rectangle& r = rects.getReference (i); -const Point RelativeParallelogram::getPointForInternalCoord (const Point* const corners, const Point& point) throw() -{ - return corners[0] - + Line (Point(), corners[1] - corners[0]).getPointAlongLine (point.getX()) - + Line (Point(), corners[2] - corners[0]).getPointAlongLine (point.getY()); + p.addRectangle ((float) r.x, + (float) r.y, + (float) r.w, + (float) r.h); + } + + return p; } END_JUCE_NAMESPACE -/*** End of inlined file: juce_RelativeCoordinate.cpp ***/ +/*** End of inlined file: juce_RectangleList.cpp ***/ /*** Start of inlined file: juce_Image.cpp ***/ diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 66bf3d2a84..9d0b9e20f0 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -64,7 +64,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 33 +#define JUCE_BUILDNUMBER 34 /** Current Juce version number. @@ -5870,6 +5870,10 @@ public: #elif JUCE_GCC #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics + #if JUCE_IPHONE + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 // (on the iphone, the 64-bit ops will compile but not link) + #endif + #else #define JUCE_ATOMICS_WINDOWS 1 // Windows with intrinsics @@ -42704,7 +42708,6 @@ public: /** Creates an absolute position from the parent origin on either the X or Y axis. @param absoluteDistanceFromOrigin the distance from the origin - @param isHorizontal this must be true if this is an X coordinate, or false if it's on the Y axis. */ RelativeCoordinate (double absoluteDistanceFromOrigin); diff --git a/src/core/juce_Atomic.h b/src/core/juce_Atomic.h index 96d65385b9..e847415349 100644 --- a/src/core/juce_Atomic.h +++ b/src/core/juce_Atomic.h @@ -169,6 +169,11 @@ public: //============================================================================== #elif JUCE_GCC #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics + + #if JUCE_IPHONE + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 // (on the iphone, the 64-bit ops will compile but not link) + #endif + //============================================================================== #else #define JUCE_ATOMICS_WINDOWS 1 // Windows with intrinsics diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index ec8a1c2c15..a35501b76c 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 33 +#define JUCE_BUILDNUMBER 34 /** Current Juce version number. diff --git a/src/events/juce_MessageManager.cpp b/src/events/juce_MessageManager.cpp index cced9ca31b..96b58de9cb 100644 --- a/src/events/juce_MessageManager.cpp +++ b/src/events/juce_MessageManager.cpp @@ -60,6 +60,10 @@ MessageManager::~MessageManager() throw() doPlatformSpecificShutdown(); + // If you hit this assertion, then you've probably leaked a Component or some other + // kind of MessageListener object... + jassert (messageListeners.size() == 0); + jassert (instance == this); instance = 0; // do this last in case this instance is still needed by doPlatformSpecificShutdown() } diff --git a/src/gui/graphics/geometry/juce_RelativeCoordinate.h b/src/gui/graphics/geometry/juce_RelativeCoordinate.h index cdae708cc2..062a549595 100644 --- a/src/gui/graphics/geometry/juce_RelativeCoordinate.h +++ b/src/gui/graphics/geometry/juce_RelativeCoordinate.h @@ -62,7 +62,6 @@ public: /** Creates an absolute position from the parent origin on either the X or Y axis. @param absoluteDistanceFromOrigin the distance from the origin - @param isHorizontal this must be true if this is an X coordinate, or false if it's on the Y axis. */ RelativeCoordinate (double absoluteDistanceFromOrigin);