First Commit

This commit is contained in:
Legaeli
2025-10-22 09:20:23 +02:00
commit a36f2ca925
4393 changed files with 1570219 additions and 0 deletions
@@ -0,0 +1,726 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace ColourHelpers
{
static uint8 floatToUInt8 (float n) noexcept
{
return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : (uint8) roundToInt (n * 255.0f));
}
static float getHue (Colour col)
{
auto r = (int) col.getRed();
auto g = (int) col.getGreen();
auto b = (int) col.getBlue();
auto hi = jmax (r, g, b);
auto lo = jmin (r, g, b);
float hue = 0.0f;
if (hi > 0 && ! exactlyEqual (hi, lo))
{
auto invDiff = 1.0f / (float) (hi - lo);
auto red = (float) (hi - r) * invDiff;
auto green = (float) (hi - g) * invDiff;
auto blue = (float) (hi - b) * invDiff;
if (r == hi) hue = blue - green;
else if (g == hi) hue = 2.0f + red - blue;
else hue = 4.0f + green - red;
hue *= 1.0f / 6.0f;
if (hue < 0.0f)
hue += 1.0f;
}
return hue;
}
//==============================================================================
struct HSL
{
HSL (Colour col) noexcept
{
auto r = (int) col.getRed();
auto g = (int) col.getGreen();
auto b = (int) col.getBlue();
auto hi = jmax (r, g, b);
auto lo = jmin (r, g, b);
if (hi < 0)
return;
lightness = ((float) (hi + lo) / 2.0f) / 255.0f;
if (lightness <= 0.0f)
return;
hue = getHue (col);
if (1.0f <= lightness)
return;
auto denominator = 1.0f - std::abs ((2.0f * lightness) - 1.0f);
saturation = ((float) (hi - lo) / 255.0f) / denominator;
}
Colour toColour (Colour original) const noexcept
{
return Colour::fromHSL (hue, saturation, lightness, original.getAlpha());
}
static PixelARGB toRGB (float h, float s, float l, uint8 alpha) noexcept
{
auto v = l < 0.5f ? l * (1.0f + s) : l + s - (l * s);
if (approximatelyEqual (v, 0.0f))
return PixelARGB (alpha, 0, 0, 0);
auto min = (2.0f * l) - v;
auto sv = (v - min) / v;
h = ((h - std::floor (h)) * 360.0f) / 60.0f;
auto f = h - std::floor (h);
auto vsf = v * sv * f;
auto mid1 = min + vsf;
auto mid2 = v - vsf;
if (h < 1.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (mid1), floatToUInt8 (min));
else if (h < 2.0f) return PixelARGB (alpha, floatToUInt8 (mid2), floatToUInt8 (v), floatToUInt8 (min));
else if (h < 3.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (v), floatToUInt8 (mid1));
else if (h < 4.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (mid2), floatToUInt8 (v));
else if (h < 5.0f) return PixelARGB (alpha, floatToUInt8 (mid1), floatToUInt8 (min), floatToUInt8 (v));
else if (h < 6.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (min), floatToUInt8 (mid2));
return PixelARGB (alpha, 0, 0, 0);
}
float hue = 0.0f, saturation = 0.0f, lightness = 0.0f;
};
//==============================================================================
struct HSB
{
HSB (Colour col) noexcept
{
auto r = (int) col.getRed();
auto g = (int) col.getGreen();
auto b = (int) col.getBlue();
auto hi = jmax (r, g, b);
auto lo = jmin (r, g, b);
if (hi > 0)
{
saturation = (float) (hi - lo) / (float) hi;
if (saturation > 0.0f)
hue = getHue (col);
brightness = (float) hi / 255.0f;
}
}
Colour toColour (Colour original) const noexcept
{
return Colour (hue, saturation, brightness, original.getAlpha());
}
static PixelARGB toRGB (float h, float s, float v, uint8 alpha) noexcept
{
v = jlimit (0.0f, 255.0f, v * 255.0f);
auto intV = (uint8) roundToInt (v);
if (s <= 0)
return PixelARGB (alpha, intV, intV, intV);
s = jmin (1.0f, s);
h = ((h - std::floor (h)) * 360.0f) / 60.0f;
auto f = h - std::floor (h);
auto x = (uint8) roundToInt (v * (1.0f - s));
if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x);
if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x);
if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))));
if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV);
if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV);
return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f)));
}
float hue = 0.0f, saturation = 0.0f, brightness = 0.0f;
};
//==============================================================================
struct YIQ
{
YIQ (Colour c) noexcept
{
auto r = c.getFloatRed();
auto g = c.getFloatGreen();
auto b = c.getFloatBlue();
y = 0.2999f * r + 0.5870f * g + 0.1140f * b;
i = 0.5957f * r - 0.2744f * g - 0.3212f * b;
q = 0.2114f * r - 0.5225f * g - 0.3113f * b;
alpha = c.getFloatAlpha();
}
Colour toColour() const noexcept
{
return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q,
y - 0.2721f * i - 0.6474f * q,
y - 1.1070f * i + 1.7046f * q,
alpha);
}
float y = 0.0f, i = 0.0f, q = 0.0f, alpha = 0.0f;
};
}
//==============================================================================
bool Colour::operator== (const Colour& other) const noexcept { return argb.getNativeARGB() == other.argb.getNativeARGB(); }
bool Colour::operator!= (const Colour& other) const noexcept { return argb.getNativeARGB() != other.argb.getNativeARGB(); }
//==============================================================================
Colour::Colour (uint32 col) noexcept
: argb (static_cast<uint8> ((col >> 24) & 0xff),
static_cast<uint8> ((col >> 16) & 0xff),
static_cast<uint8> ((col >> 8) & 0xff),
static_cast<uint8> (col & 0xff))
{
}
Colour::Colour (uint8 red, uint8 green, uint8 blue) noexcept
{
argb.setARGB (0xff, red, green, blue);
}
Colour Colour::fromRGB (uint8 red, uint8 green, uint8 blue) noexcept
{
return Colour (red, green, blue);
}
Colour::Colour (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept
{
argb.setARGB (alpha, red, green, blue);
}
Colour Colour::fromRGBA (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept
{
return Colour (red, green, blue, alpha);
}
Colour::Colour (uint8 red, uint8 green, uint8 blue, float alpha) noexcept
{
argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue);
}
Colour Colour::fromFloatRGBA (float red, float green, float blue, float alpha) noexcept
{
return Colour (ColourHelpers::floatToUInt8 (red),
ColourHelpers::floatToUInt8 (green),
ColourHelpers::floatToUInt8 (blue), alpha);
}
Colour::Colour (float hue, float saturation, float brightness, float alpha) noexcept
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha)))
{
}
Colour Colour::fromHSV (float hue, float saturation, float brightness, float alpha) noexcept
{
return Colour (hue, saturation, brightness, alpha);
}
Colour Colour::fromHSL (float hue, float saturation, float lightness, float alpha) noexcept
{
Colour hslColour;
hslColour.argb = ColourHelpers::HSL::toRGB (hue, saturation, lightness, ColourHelpers::floatToUInt8 (alpha));
return hslColour;
}
Colour::Colour (float hue, float saturation, float brightness, uint8 alpha) noexcept
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha))
{
}
Colour::Colour (PixelARGB argb_) noexcept
: argb (argb_)
{
}
Colour::Colour (PixelRGB rgb) noexcept
: argb (Colour (rgb.getInARGBMaskOrder()).argb)
{
}
Colour::Colour (PixelAlpha alpha) noexcept
: argb (Colour (alpha.getInARGBMaskOrder()).argb)
{
}
//==============================================================================
PixelARGB Colour::getPixelARGB() const noexcept
{
PixelARGB p (argb);
p.premultiply();
return p;
}
PixelARGB Colour::getNonPremultipliedPixelARGB() const noexcept
{
return argb;
}
uint32 Colour::getARGB() const noexcept
{
return argb.getInARGBMaskOrder();
}
//==============================================================================
bool Colour::isTransparent() const noexcept
{
return getAlpha() == 0;
}
bool Colour::isOpaque() const noexcept
{
return getAlpha() == 0xff;
}
Colour Colour::withAlpha (uint8 newAlpha) const noexcept
{
PixelARGB newCol (argb);
newCol.setAlpha (newAlpha);
return Colour (newCol);
}
Colour Colour::withAlpha (float newAlpha) const noexcept
{
jassert (newAlpha >= 0 && newAlpha <= 1.0f);
PixelARGB newCol (argb);
newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha));
return Colour (newCol);
}
Colour Colour::withMultipliedAlpha (float alphaMultiplier) const noexcept
{
jassert (alphaMultiplier >= 0);
PixelARGB newCol (argb);
newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha())));
return Colour (newCol);
}
//==============================================================================
Colour Colour::overlaidWith (Colour src) const noexcept
{
auto destAlpha = getAlpha();
if (destAlpha <= 0)
return src;
auto invA = 0xff - (int) src.getAlpha();
auto resA = 0xff - (((0xff - destAlpha) * invA) >> 8);
if (resA <= 0)
return *this;
auto 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);
}
Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept
{
if (proportionOfOther <= 0)
return *this;
if (proportionOfOther >= 1.0f)
return other;
PixelARGB c1 (getPixelARGB());
PixelARGB c2 (other.getPixelARGB());
c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f));
c1.unpremultiply();
return Colour (c1);
}
//==============================================================================
float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; }
float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; }
float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; }
float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; }
//==============================================================================
void Colour::getHSB (float& h, float& s, float& v) const noexcept
{
ColourHelpers::HSB hsb (*this);
h = hsb.hue;
s = hsb.saturation;
v = hsb.brightness;
}
void Colour::getHSL (float& h, float& s, float& l) const noexcept
{
ColourHelpers::HSL hsl (*this);
h = hsl.hue;
s = hsl.saturation;
l = hsl.lightness;
}
float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; }
float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; }
float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; }
float Colour::getSaturationHSL() const noexcept { return ColourHelpers::HSL (*this).saturation; }
float Colour::getLightness() const noexcept { return ColourHelpers::HSL (*this).lightness; }
Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); }
Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); }
Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); }
Colour Colour::withSaturationHSL (float s) const noexcept { ColourHelpers::HSL hsl (*this); hsl.saturation = s; return hsl.toColour (*this); }
Colour Colour::withLightness (float l) const noexcept { ColourHelpers::HSL hsl (*this); hsl.lightness = l; return hsl.toColour (*this); }
float Colour::getPerceivedBrightness() const noexcept
{
return std::sqrt (0.241f * square (getFloatRed())
+ 0.691f * square (getFloatGreen())
+ 0.068f * square (getFloatBlue()));
}
//==============================================================================
Colour Colour::withRotatedHue (float amountToRotate) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.hue += amountToRotate;
return hsb.toColour (*this);
}
Colour Colour::withMultipliedSaturation (float amount) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.saturation = jmin (1.0f, hsb.saturation * amount);
return hsb.toColour (*this);
}
Colour Colour::withMultipliedSaturationHSL (float amount) const noexcept
{
ColourHelpers::HSL hsl (*this);
hsl.saturation = jmin (1.0f, hsl.saturation * amount);
return hsl.toColour (*this);
}
Colour Colour::withMultipliedBrightness (float amount) const noexcept
{
ColourHelpers::HSB hsb (*this);
hsb.brightness = jmin (1.0f, hsb.brightness * amount);
return hsb.toColour (*this);
}
Colour Colour::withMultipliedLightness (float amount) const noexcept
{
ColourHelpers::HSL hsl (*this);
hsl.lightness = jmin (1.0f, hsl.lightness * amount);
return hsl.toColour (*this);
}
//==============================================================================
Colour Colour::brighter (float amount) const noexcept
{
jassert (amount >= 0.0f);
amount = 1.0f / (1.0f + amount);
return Colour ((uint8) (255 - (amount * (255 - getRed()))),
(uint8) (255 - (amount * (255 - getGreen()))),
(uint8) (255 - (amount * (255 - getBlue()))),
getAlpha());
}
Colour Colour::darker (float amount) const noexcept
{
jassert (amount >= 0.0f);
amount = 1.0f / (1.0f + amount);
return Colour ((uint8) (amount * getRed()),
(uint8) (amount * getGreen()),
(uint8) (amount * getBlue()),
getAlpha());
}
//==============================================================================
Colour Colour::greyLevel (float brightness) noexcept
{
auto level = ColourHelpers::floatToUInt8 (brightness);
return Colour (level, level, level);
}
//==============================================================================
Colour Colour::contrasting (float amount) const noexcept
{
return overlaidWith ((getPerceivedBrightness() >= 0.5f
? Colours::black
: Colours::white).withAlpha (amount));
}
Colour Colour::contrasting (Colour target, float minContrast) const noexcept
{
ColourHelpers::YIQ bg (*this);
ColourHelpers::YIQ fg (target);
if (std::abs (bg.y - fg.y) >= minContrast)
return target;
auto y1 = jmax (0.0f, bg.y - minContrast);
auto y2 = jmin (1.0f, bg.y + minContrast);
fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2;
return fg.toColour();
}
Colour Colour::contrasting (Colour colour1,
Colour colour2) noexcept
{
auto b1 = colour1.getPerceivedBrightness();
auto b2 = colour2.getPerceivedBrightness();
float best = 0.0f, bestDist = 0.0f;
for (float i = 0.0f; i < 1.0f; i += 0.02f)
{
auto d1 = std::abs (i - b1);
auto d2 = std::abs (i - b2);
auto dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2);
if (dist > bestDist)
{
best = i;
bestDist = dist;
}
}
return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f))
.withBrightness (best);
}
//==============================================================================
String Colour::toString() const
{
return String::toHexString ((int) argb.getInARGBMaskOrder());
}
Colour Colour::fromString (StringRef encodedColourString)
{
return Colour (CharacterFunctions::HexParser<uint32>::parse (encodedColourString.text));
}
String Colour::toDisplayString (const bool includeAlphaValue) const
{
return String::toHexString ((int) (argb.getInARGBMaskOrder() & (includeAlphaValue ? 0xffffffff : 0xffffff)))
.paddedLeft ('0', includeAlphaValue ? 8 : 6)
.toUpperCase();
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class ColourTests final : public UnitTest
{
public:
ColourTests()
: UnitTest ("Colour", UnitTestCategories::graphics)
{}
void runTest() override
{
auto testColour = [this] (Colour colour,
uint8 expectedRed, uint8 expectedGreen, uint8 expectedBlue,
uint8 expectedAlpha = 255, float expectedFloatAlpha = 1.0f)
{
expectEquals (colour.getRed(), expectedRed);
expectEquals (colour.getGreen(), expectedGreen);
expectEquals (colour.getBlue(), expectedBlue);
expectEquals (colour.getAlpha(), expectedAlpha);
expectEquals (colour.getFloatAlpha(), expectedFloatAlpha);
};
beginTest ("Constructors");
{
Colour c1;
testColour (c1, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c2 ((uint32) 0);
testColour (c2, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c3 ((uint32) 0xffffffff);
testColour (c3, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c4 (0, 0, 0);
testColour (c4, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 255, 1.0f);
Colour c5 (255, 255, 255);
testColour (c5, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c6 ((uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0);
testColour (c6, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c7 ((uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255);
testColour (c7, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
Colour c8 ((uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
testColour (c8, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
Colour c9 ((uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
testColour (c9, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
}
beginTest ("HSV");
{
// black
testColour (Colour::fromHSV (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
// white
testColour (Colour::fromHSV (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
// red
testColour (Colour::fromHSV (0.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
testColour (Colour::fromHSV (1.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
// lime
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 0);
// blue
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 0, 255);
// yellow
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 255, 0);
// cyan
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 255);
// magenta
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 0, 255);
// silver
testColour (Colour::fromHSV (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
// grey
testColour (Colour::fromHSV (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
// maroon
testColour (Colour::fromHSV (0.0f, 1.0f, 0.5f, 1.0f), 128, 0, 0);
// olive
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 128, 0);
// green
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 0);
// purple
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 0, 128);
// teal
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 128);
// navy
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 128);
}
beginTest ("HSL");
{
// black
testColour (Colour::fromHSL (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
// white
testColour (Colour::fromHSL (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
// red
testColour (Colour::fromHSL (0.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
testColour (Colour::fromHSL (1.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
// lime
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 0);
// blue
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 255);
// yellow
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 255, 0);
// cyan
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 255);
// magenta
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 0, 255);
// silver
testColour (Colour::fromHSL (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
// grey
testColour (Colour::fromHSL (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
// maroon
testColour (Colour::fromHSL (0.0f, 1.0f, 0.25f, 1.0f), 128, 0, 0);
// olive
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 128, 0);
// green
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 0);
// purple
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 0, 128);
// teal
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 128);
// navy
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 0, 128);
}
beginTest ("Modifiers");
{
Colour red (255, 0, 0);
testColour (red, 255, 0, 0);
testColour (red.withHue (120.0f / 360.0f), 0, 255, 0);
testColour (red.withSaturation (0.5f), 255, 128, 128);
testColour (red.withSaturationHSL (0.5f), 191, 64, 64);
testColour (red.withBrightness (0.5f), 128, 0, 0);
testColour (red.withLightness (1.0f), 255, 255, 255);
testColour (red.withRotatedHue (120.0f / 360.0f), 0, 255, 0);
testColour (red.withRotatedHue (480.0f / 360.0f), 0, 255, 0);
testColour (red.withRotatedHue (-240.0f / 360.0f), 0, 255, 0);
testColour (red.withRotatedHue (-600.0f / 360.0f), 0, 255, 0);
testColour (red.withMultipliedSaturation (0.0f), 255, 255, 255);
testColour (red.withMultipliedSaturationHSL (0.0f), 128, 128, 128);
testColour (red.withMultipliedBrightness (0.5f), 128, 0, 0);
testColour (red.withMultipliedLightness (2.0f), 255, 255, 255);
testColour (red.withMultipliedLightness (1.0f), 255, 0, 0);
testColour (red.withLightness (red.getLightness()), 255, 0, 0);
}
}
};
static ColourTests colourTests;
#endif
} // namespace juce
@@ -0,0 +1,429 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a colour, also including a transparency value.
The colour is stored internally as unsigned 8-bit red, green, blue and alpha values.
@tags{Graphics}
*/
class JUCE_API Colour final
{
public:
//==============================================================================
/** Creates a transparent black colour. */
Colour() = default;
/** Creates a copy of another Colour object. */
Colour (const Colour&) = default;
/** Creates a colour from a 32-bit ARGB value.
The format of this number is:
((alpha << 24) | (red << 16) | (green << 8) | blue).
All components in the range 0x00 to 0xff.
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque.
@see getPixelARGB
*/
explicit Colour (uint32 argb) noexcept;
/** Creates an opaque colour using 8-bit red, green and blue values */
Colour (uint8 red,
uint8 green,
uint8 blue) noexcept;
/** Creates an opaque colour using 8-bit red, green and blue values */
static Colour fromRGB (uint8 red,
uint8 green,
uint8 blue) noexcept;
/** Creates a colour using 8-bit red, green, blue and alpha values. */
Colour (uint8 red,
uint8 green,
uint8 blue,
uint8 alpha) noexcept;
/** Creates a colour using 8-bit red, green, blue and alpha values. */
static Colour fromRGBA (uint8 red,
uint8 green,
uint8 blue,
uint8 alpha) noexcept;
/** Creates a colour from 8-bit red, green, and blue values, and a floating-point alpha.
Alpha of 0.0 is transparent, alpha of 1.0f is opaque.
Values outside the valid range will be clipped.
*/
Colour (uint8 red,
uint8 green,
uint8 blue,
float alpha) noexcept;
/** Creates a colour using floating point red, green, blue and alpha values.
Numbers outside the range 0..1 will be clipped.
*/
static Colour fromFloatRGBA (float red,
float green,
float blue,
float alpha) noexcept;
/** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha.
The floating point values must be between 0.0 and 1.0.
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque.
Values outside the valid range will be clipped.
*/
Colour (float hue,
float saturation,
float brightness,
uint8 alpha) noexcept;
/** Creates a colour using floating point hue, saturation, brightness and alpha values.
All values must be between 0.0 and 1.0.
Numbers outside the valid range will be clipped.
*/
Colour (float hue,
float saturation,
float brightness,
float alpha) noexcept;
/** Creates a colour using floating point hue, saturation, brightness and alpha values.
All values must be between 0.0 and 1.0.
Numbers outside the valid range will be clipped.
*/
static Colour fromHSV (float hue,
float saturation,
float brightness,
float alpha) noexcept;
/** Creates a colour using floating point hue, saturation, lightness and alpha values.
All values must be between 0.0 and 1.0.
Numbers outside the valid range will be clipped.
*/
static Colour fromHSL (float hue,
float saturation,
float lightness,
float alpha) noexcept;
/** Creates a colour using a PixelARGB object. This function assumes that the argb pixel is
not premultiplied.
*/
Colour (PixelARGB argb) noexcept;
/** Creates a colour using a PixelRGB object.
*/
Colour (PixelRGB rgb) noexcept;
/** Creates a colour using a PixelAlpha object.
*/
Colour (PixelAlpha alpha) noexcept;
/** Destructor. */
~Colour() = default;
/** Copies another Colour object. */
Colour& operator= (const Colour&) = default;
/** Compares two colours. */
bool operator== (const Colour& other) const noexcept;
/** Compares two colours. */
bool operator!= (const Colour& other) const noexcept;
//==============================================================================
/** Returns the red component of this colour.
@returns a value between 0x00 and 0xff.
*/
uint8 getRed() const noexcept { return argb.getRed(); }
/** Returns the green component of this colour.
@returns a value between 0x00 and 0xff.
*/
uint8 getGreen() const noexcept { return argb.getGreen(); }
/** Returns the blue component of this colour.
@returns a value between 0x00 and 0xff.
*/
uint8 getBlue() const noexcept { return argb.getBlue(); }
/** Returns the red component of this colour as a floating point value.
@returns a value between 0.0 and 1.0
*/
float getFloatRed() const noexcept;
/** Returns the green component of this colour as a floating point value.
@returns a value between 0.0 and 1.0
*/
float getFloatGreen() const noexcept;
/** Returns the blue component of this colour as a floating point value.
@returns a value between 0.0 and 1.0
*/
float getFloatBlue() const noexcept;
/** Returns a premultiplied ARGB pixel object that represents this colour.
*/
PixelARGB getPixelARGB() const noexcept;
/** Returns an ARGB pixel object that represents this colour.
*/
PixelARGB getNonPremultipliedPixelARGB() const noexcept;
/** Returns a 32-bit integer that represents this colour.
The format of this number is:
((alpha << 24) | (red << 16) | (green << 8) | blue).
*/
uint32 getARGB() const noexcept;
//==============================================================================
/** Returns the colour's alpha (opacity).
Alpha of 0x00 is completely transparent, 0xff is completely opaque.
*/
uint8 getAlpha() const noexcept { return argb.getAlpha(); }
/** Returns the colour's alpha (opacity) as a floating point value.
Alpha of 0.0 is completely transparent, 1.0 is completely opaque.
*/
float getFloatAlpha() const noexcept;
/** Returns true if this colour is completely opaque.
Equivalent to (getAlpha() == 0xff).
*/
bool isOpaque() const noexcept;
/** Returns true if this colour is completely transparent.
Equivalent to (getAlpha() == 0x00).
*/
bool isTransparent() const noexcept;
/** Returns a colour that's the same colour as this one, but with a new alpha value. */
Colour withAlpha (uint8 newAlpha) const noexcept;
/** Returns a colour that's the same colour as this one, but with a new alpha value. */
Colour withAlpha (float newAlpha) const noexcept;
/** Returns a colour that's the same colour as this one, but with a modified alpha value.
The new colour's alpha will be this object's alpha multiplied by the value passed-in.
*/
Colour withMultipliedAlpha (float alphaMultiplier) const noexcept;
//==============================================================================
/** Returns a colour that is the result of alpha-compositing a new colour over this one.
If the foreground colour is semi-transparent, it is blended onto this colour accordingly.
*/
Colour overlaidWith (Colour foregroundColour) const noexcept;
/** Returns a colour that lies somewhere between this one and another.
If amountOfOther is zero, the result is 100% this colour, if amountOfOther
is 1.0, the result is 100% of the other colour.
*/
Colour interpolatedWith (Colour other, float proportionOfOther) const noexcept;
//==============================================================================
/** Returns the colour's hue component.
The value returned is in the range 0.0 to 1.0
*/
float getHue() const noexcept;
/** Returns the colour's saturation component.
The value returned is in the range 0.0 to 1.0
*/
float getSaturation() const noexcept;
/** Returns the colour's saturation component as represented in the HSL colour space.
The value returned is in the range 0.0 to 1.0
*/
float getSaturationHSL() const noexcept;
/** Returns the colour's brightness component.
The value returned is in the range 0.0 to 1.0
*/
float getBrightness() const noexcept;
/** Returns the colour's lightness component.
The value returned is in the range 0.0 to 1.0
*/
float getLightness() const noexcept;
/** Returns a skewed brightness value, adjusted to better reflect the way the human
eye responds to different colour channels. This makes it better than getBrightness()
for comparing differences in brightness.
*/
float getPerceivedBrightness() const noexcept;
/** Returns the colour's hue, saturation and brightness components all at once.
The values returned are in the range 0.0 to 1.0
*/
void getHSB (float& hue,
float& saturation,
float& brightness) const noexcept;
/** Returns the colour's hue, saturation and lightness components all at once.
The values returned are in the range 0.0 to 1.0
*/
void getHSL (float& hue,
float& saturation,
float& lightness) const noexcept;
//==============================================================================
/** Returns a copy of this colour with a different hue. */
[[nodiscard]] Colour withHue (float newHue) const noexcept;
/** Returns a copy of this colour with a different saturation. */
[[nodiscard]] Colour withSaturation (float newSaturation) const noexcept;
/** Returns a copy of this colour with a different saturation in the HSL colour space. */
[[nodiscard]] Colour withSaturationHSL (float newSaturation) const noexcept;
/** Returns a copy of this colour with a different brightness.
@see brighter, darker, withMultipliedBrightness
*/
[[nodiscard]] Colour withBrightness (float newBrightness) const noexcept;
/** Returns a copy of this colour with a different lightness.
@see lighter, darker, withMultipliedLightness
*/
[[nodiscard]] Colour withLightness (float newLightness) const noexcept;
/** Returns a copy of this colour with its hue rotated.
The new colour's hue is ((this->getHue() + amountToRotate) % 1.0)
@see brighter, darker, withMultipliedBrightness
*/
[[nodiscard]] Colour withRotatedHue (float amountToRotate) const noexcept;
/** Returns a copy of this colour with its saturation multiplied by the given value.
The new colour's saturation is (this->getSaturation() * multiplier)
(the result is clipped to legal limits).
*/
[[nodiscard]] Colour withMultipliedSaturation (float multiplier) const noexcept;
/** Returns a copy of this colour with its saturation multiplied by the given value.
The new colour's saturation is (this->getSaturation() * multiplier)
(the result is clipped to legal limits).
This will be in the HSL colour space.
*/
[[nodiscard]] Colour withMultipliedSaturationHSL (float multiplier) const noexcept;
/** Returns a copy of this colour with its brightness multiplied by the given value.
The new colour's brightness is (this->getBrightness() * multiplier)
(the result is clipped to legal limits).
*/
[[nodiscard]] Colour withMultipliedBrightness (float amount) const noexcept;
/** Returns a copy of this colour with its lightness multiplied by the given value.
The new colour's lightness is (this->lightness() * multiplier)
(the result is clipped to legal limits).
*/
[[nodiscard]] Colour withMultipliedLightness (float amount) const noexcept;
//==============================================================================
/** Returns a brighter version of this colour.
@param amountBrighter how much brighter to make it - a value greater than or equal to 0,
where 0 is unchanged, and higher values make it brighter
@see withMultipliedBrightness
*/
[[nodiscard]] Colour brighter (float amountBrighter = 0.4f) const noexcept;
/** Returns a darker version of this colour.
@param amountDarker how much darker to make it - a value greater than or equal to 0,
where 0 is unchanged, and higher values make it darker
@see withMultipliedBrightness
*/
[[nodiscard]] Colour darker (float amountDarker = 0.4f) const noexcept;
//==============================================================================
/** Returns a colour that will be clearly visible against this colour.
The amount parameter indicates how contrasting the new colour should
be, so e.g. Colours::black.contrasting (0.1f) will return a colour
that's just a little bit lighter; Colours::black.contrasting (1.0f) will
return white; Colours::white.contrasting (1.0f) will return black, etc.
*/
[[nodiscard]] Colour contrasting (float amount = 1.0f) const noexcept;
/** Returns a colour that is as close as possible to a target colour whilst
still being in contrast to this one.
The colour that is returned will be the targetColour, but with its luminosity
nudged up or down so that it differs from the luminosity of this colour
by at least the amount specified by minLuminosityDiff.
*/
[[nodiscard]] Colour contrasting (Colour targetColour, float minLuminosityDiff) const noexcept;
/** Returns a colour that contrasts against two colours.
Looks for a colour that contrasts with both of the colours passed-in.
Handy for things like choosing a highlight colour in text editors, etc.
*/
[[nodiscard]] static Colour contrasting (Colour colour1,
Colour colour2) noexcept;
//==============================================================================
/** Returns an opaque shade of grey.
@param brightness the level of grey to return - 0 is black, 1.0 is white
*/
[[nodiscard]] static Colour greyLevel (float brightness) noexcept;
//==============================================================================
/** Returns a stringified version of this colour.
The string can be turned back into a colour using the fromString() method.
*/
String toString() const;
/** Reads the colour from a string that was created with toString(). */
[[nodiscard]] static Colour fromString (StringRef encodedColourString);
/** Returns the colour as a hex string in the form RRGGBB or AARRGGBB. */
String toDisplayString (bool includeAlphaValue) const;
private:
//==============================================================================
PixelARGB argb { 0, 0, 0, 0 };
};
} // namespace juce
@@ -0,0 +1,294 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
ColourGradient::ColourGradient() noexcept : isRadial (false)
{
#if JUCE_DEBUG
point1.setX (987654.0f);
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (! exactlyEqual (point1.x, 987654.0f));
#else
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED
#endif
}
ColourGradient::ColourGradient (const ColourGradient& other)
: point1 (other.point1), point2 (other.point2), isRadial (other.isRadial), colours (other.colours)
{}
ColourGradient::ColourGradient (ColourGradient&& other) noexcept
: point1 (other.point1), point2 (other.point2), isRadial (other.isRadial),
colours (std::move (other.colours))
{}
ColourGradient& ColourGradient::operator= (const ColourGradient& other)
{
point1 = other.point1;
point2 = other.point2;
isRadial = other.isRadial;
colours = other.colours;
return *this;
}
ColourGradient& ColourGradient::operator= (ColourGradient&& other) noexcept
{
point1 = other.point1;
point2 = other.point2;
isRadial = other.isRadial;
colours = std::move (other.colours);
return *this;
}
ColourGradient::ColourGradient (Colour colour1, float x1, float y1,
Colour colour2, float x2, float y2, bool radial)
: ColourGradient (colour1, Point<float> (x1, y1),
colour2, Point<float> (x2, y2), radial)
{
}
ColourGradient::ColourGradient (Colour colour1, Point<float> p1,
Colour colour2, Point<float> p2, bool radial)
: point1 (p1),
point2 (p2),
isRadial (radial)
{
colours.add (ColourPoint { 0.0, colour1 },
ColourPoint { 1.0, colour2 });
}
ColourGradient ColourGradient::vertical (Colour c1, float y1, Colour c2, float y2)
{
return { c1, 0, y1, c2, 0, y2, false };
}
ColourGradient ColourGradient::horizontal (Colour c1, float x1, Colour c2, float x2)
{
return { c1, x1, 0, c2, x2, 0, false };
}
struct PointComparisons
{
auto tie() const { return std::tie (point->x, point->y); }
bool operator== (const PointComparisons& other) const { return tie() == other.tie(); }
bool operator!= (const PointComparisons& other) const { return tie() != other.tie(); }
bool operator< (const PointComparisons& other) const { return tie() < other.tie(); }
const Point<float>* point = nullptr;
};
struct ColourGradient::ColourPointArrayComparisons
{
bool operator== (const ColourPointArrayComparisons& other) const { return *array == *other.array; }
bool operator!= (const ColourPointArrayComparisons& other) const { return *array != *other.array; }
bool operator< (const ColourPointArrayComparisons& other) const
{
return std::lexicographical_compare (array->begin(), array->end(), other.array->begin(), other.array->end());
}
const Array<ColourGradient::ColourPoint>* array = nullptr;
};
auto ColourGradient::tie() const
{
return std::tuple (PointComparisons { &point1 },
PointComparisons { &point2 },
isRadial,
ColourPointArrayComparisons { &colours });
}
bool ColourGradient::operator== (const ColourGradient& other) const noexcept { return tie() == other.tie(); }
bool ColourGradient::operator!= (const ColourGradient& other) const noexcept { return tie() != other.tie(); }
bool ColourGradient::operator< (const ColourGradient& other) const noexcept { return tie() < other.tie(); }
bool ColourGradient::operator<= (const ColourGradient& other) const noexcept { return tie() <= other.tie(); }
bool ColourGradient::operator> (const ColourGradient& other) const noexcept { return tie() > other.tie(); }
bool ColourGradient::operator>= (const ColourGradient& other) const noexcept { return tie() >= other.tie(); }
//==============================================================================
void ColourGradient::clearColours()
{
colours.clear();
}
int ColourGradient::addColour (const double proportionAlongGradient, Colour colour)
{
// must be within the two end-points
jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0);
if (proportionAlongGradient <= 0)
{
colours.set (0, { 0.0, colour });
return 0;
}
auto pos = jmin (1.0, proportionAlongGradient);
int i;
for (i = 0; i < colours.size(); ++i)
if (colours.getReference (i).position > pos)
break;
colours.insert (i, { pos, colour });
return i;
}
void ColourGradient::removeColour (int index)
{
jassert (isPositiveAndBelow (index, colours.size()));
colours.remove (index);
}
void ColourGradient::multiplyOpacity (const float multiplier) noexcept
{
for (auto& c : colours)
c.colour = c.colour.withMultipliedAlpha (multiplier);
}
//==============================================================================
int ColourGradient::getNumColours() const noexcept
{
return colours.size();
}
double ColourGradient::getColourPosition (int index) const noexcept
{
if (isPositiveAndBelow (index, colours.size()))
return colours.getReference (index).position;
return 0;
}
Colour ColourGradient::getColour (int index) const noexcept
{
if (isPositiveAndBelow (index, colours.size()))
return colours.getReference (index).colour;
return {};
}
void ColourGradient::setColour (int index, Colour newColour) noexcept
{
if (isPositiveAndBelow (index, colours.size()))
colours.getReference (index).colour = newColour;
}
Colour ColourGradient::getColourAtPosition (double position) const noexcept
{
jassert (approximatelyEqual (colours.getReference (0).position, 0.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;
auto& p1 = colours.getReference (i);
if (i >= colours.size() - 1)
return p1.colour;
auto& p2 = colours.getReference (i + 1);
return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position)));
}
//==============================================================================
void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept
{
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates?
jassert (colours.size() >= 2);
jassert (numEntries > 0);
jassert (approximatelyEqual (colours.getReference (0).position, 0.0)); // The first colour specified has to go at position 0
int index = 0;
for (int j = 0; j < colours.size() - 1; ++j)
{
const auto& o = colours.getReference (j + 0);
const auto& p = colours.getReference (j + 1);
const auto numToDo = roundToInt (p.position * (numEntries - 1)) - index;
const auto pix1 = o.colour.getNonPremultipliedPixelARGB();
const auto pix2 = p.colour.getNonPremultipliedPixelARGB();
for (auto i = 0; i < numToDo; ++i)
{
auto blended = pix1;
blended.tween (pix2, (uint32) ((i << 8) / numToDo));
blended.premultiply();
jassert (0 <= index && index < numEntries);
lookupTable[index++] = blended;
}
}
std::fill (lookupTable + index, lookupTable + numEntries, colours.getLast().colour.getPixelARGB());
}
int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& lookupTable) const
{
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates?
jassert (colours.size() >= 2);
auto numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8),
3 * (int) point1.transformedBy (transform)
.getDistanceFrom (point2.transformedBy (transform)));
lookupTable.malloc (numEntries);
createLookupTable (lookupTable, numEntries);
return numEntries;
}
bool ColourGradient::isOpaque() const noexcept
{
for (auto& c : colours)
if (! c.colour.isOpaque())
return false;
return true;
}
bool ColourGradient::isInvisible() const noexcept
{
for (auto& c : colours)
if (! c.colour.isTransparent())
return false;
return true;
}
} // namespace juce
@@ -0,0 +1,255 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Describes the layout and colours that should be used to paint a colour gradient.
@see Graphics::setGradientFill
@tags{Graphics}
*/
class JUCE_API ColourGradient final
{
public:
/** Creates an uninitialised gradient.
If you use this constructor instead of the other one, be sure to set all the
object's public member variables before using it!
*/
ColourGradient() noexcept;
ColourGradient (const ColourGradient&);
ColourGradient (ColourGradient&&) noexcept;
ColourGradient& operator= (const ColourGradient&);
ColourGradient& operator= (ColourGradient&&) noexcept;
//==============================================================================
/** Creates a gradient object.
(x1, y1) is the location to draw with colour1. Likewise (x2, y2) is where
colour2 should be. In between them there's a gradient.
If isRadial is true, the colours form a circular gradient with (x1, y1) at
its centre.
The alpha transparencies of the colours are used, so note that
if you blend from transparent to a solid colour, the RGB of the transparent
colour will become visible in parts of the gradient. e.g. blending
from Colour::transparentBlack to Colours::white will produce a
muddy grey colour midway, but Colour::transparentWhite to Colours::white
will be white all the way across.
@see ColourGradient
*/
ColourGradient (Colour colour1, float x1, float y1,
Colour colour2, float x2, float y2,
bool isRadial);
/** Creates a gradient object.
point1 is the location to draw with colour1. Likewise point2 is where
colour2 should be. In between them there's a gradient.
If isRadial is true, the colours form a circular gradient with point1 at
its centre.
The alpha transparencies of the colours are used, so note that
if you blend from transparent to a solid colour, the RGB of the transparent
colour will become visible in parts of the gradient. e.g. blending
from Colour::transparentBlack to Colours::white will produce a
muddy grey colour midway, but Colour::transparentWhite to Colours::white
will be white all the way across.
@see ColourGradient
*/
ColourGradient (Colour colour1, Point<float> point1,
Colour colour2, Point<float> point2,
bool isRadial);
//==============================================================================
/** Creates a vertical linear gradient between two Y coordinates */
static ColourGradient vertical (Colour colour1, float y1,
Colour colour2, float y2);
/** Creates a horizontal linear gradient between two X coordinates */
static ColourGradient horizontal (Colour colour1, float x1,
Colour colour2, float x2);
/** Creates a vertical linear gradient from top to bottom in a rectangle */
template <typename Type>
static ColourGradient vertical (Colour colourTop, Colour colourBottom, Rectangle<Type> area)
{
return vertical (colourTop, (float) area.getY(), colourBottom, (float) area.getBottom());
}
/** Creates a horizontal linear gradient from right to left in a rectangle */
template <typename Type>
static ColourGradient horizontal (Colour colourLeft, Colour colourRight, Rectangle<Type> area)
{
return horizontal (colourLeft, (float) area.getX(), colourRight, (float) area.getRight());
}
//==============================================================================
/** Removes any colours that have been added.
This will also remove any start and end colours, so the gradient won't work. You'll
need to add more colours with addColour().
*/
void clearColours();
/** Adds a colour at a point along the length of the gradient.
This allows the gradient to go through a spectrum of colours, instead of just a
start and end colour.
@param proportionAlongGradient a value between 0 and 1.0, which is the proportion
of the distance along the line between the two points
at which the colour should occur.
@param colour the colour that should be used at this point
@returns the index at which the new point was added
*/
int addColour (double proportionAlongGradient, Colour colour);
/** Removes one of the colours from the gradient. */
void removeColour (int index);
/** Multiplies the alpha value of all the colours by the given scale factor */
void multiplyOpacity (float multiplier) noexcept;
//==============================================================================
/** Returns the number of colour-stops that have been added. */
int getNumColours() const noexcept;
/** Returns the position along the length of the gradient of the colour with this index.
The index is from 0 to getNumColours() - 1. The return value will be between 0.0 and 1.0
*/
double getColourPosition (int index) const noexcept;
/** Returns the colour that was added with a given index.
The index is from 0 to getNumColours() - 1.
*/
Colour getColour (int index) const noexcept;
/** Changes the colour at a given index.
The index is from 0 to getNumColours() - 1.
*/
void setColour (int index, Colour newColour) noexcept;
/** Returns the an interpolated colour at any position along the gradient.
@param position the position along the gradient, between 0 and 1
*/
Colour getColourAtPosition (double position) const noexcept;
//==============================================================================
/** Creates a set of interpolated premultiplied ARGB values.
This will resize the HeapBlock, fill it with the colours, and will return the number of
colours that it added.
When calling this, the ColourGradient must have at least 2 colour stops specified.
*/
int createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& resultLookupTable) const;
/** Creates a set of interpolated premultiplied ARGB values.
This will fill an array of a user-specified size with the gradient, interpolating to fit.
The numEntries argument specifies the size of the array, and this size must be greater than zero.
When calling this, the ColourGradient must have at least 2 colour stops specified.
*/
void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept;
/** Creates a set of interpolated premultiplied ARGB values.
This will fill an array of a user-specified size with the gradient, interpolating to fit.
When calling this, the ColourGradient must have at least 2 colour stops specified.
*/
template <size_t NumEntries>
void createLookupTable (PixelARGB (&resultLookupTable)[NumEntries]) const noexcept
{
static_assert (NumEntries != 0);
createLookupTable (resultLookupTable, NumEntries);
}
/** Returns true if all colours are opaque. */
bool isOpaque() const noexcept;
/** Returns true if all colours are completely transparent. */
bool isInvisible() const noexcept;
//==============================================================================
Point<float> point1, point2;
/** If true, the gradient should be filled circularly, centred around
point1, with point2 defining a point on the circumference.
If false, the gradient is linear between the two points.
*/
bool isRadial;
bool operator== (const ColourGradient&) const noexcept;
bool operator!= (const ColourGradient&) const noexcept;
/** This comparison, and the other ordered comparisons are provided only for compatibility with
ordered container types like std::set and std::map.
*/
bool operator< (const ColourGradient&) const noexcept;
bool operator<= (const ColourGradient&) const noexcept;
bool operator> (const ColourGradient&) const noexcept;
bool operator>= (const ColourGradient&) const noexcept;
private:
//==============================================================================
struct ColourPoint
{
auto tie() const { return std::tuple (position, colour.getPixelARGB().getNativeARGB()); }
bool operator== (ColourPoint other) const noexcept { return tie() == other.tie(); }
bool operator!= (ColourPoint other) const noexcept { return tie() != other.tie(); }
bool operator< (ColourPoint other) const noexcept { return tie() < other.tie(); }
double position;
Colour colour;
};
struct ColourPointArrayComparisons;
auto tie() const;
Array<ColourPoint> colours;
JUCE_LEAK_DETECTOR (ColourGradient)
};
} // namespace juce
@@ -0,0 +1,205 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
Colour Colours::findColourForName (const String& colourName,
Colour defaultColour)
{
struct StringHashAndColour { uint32 stringHash, colour; };
static const StringHashAndColour presets[]
{
{ 0x05978fff, 0xff000000 }, /* black */
{ 0x06bdcc29, 0xffffffff }, /* white */
{ 0x002e305a, 0xff0000ff }, /* blue */
{ 0x00308adf, 0xff808080 }, /* grey */
{ 0x00308a63, 0xff808080 }, /* gray */
{ 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 */
{ 0x67ceced9, 0xff555555 }, /* darkgray */
{ 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 */
{ 0xc2b34cc6, 0xff2f4f4f }, /* darkslategray */
{ 0x7cf2b06b, 0xff00ced1 }, /* darkturquoise */
{ 0xc8769375, 0xff9400d3 }, /* darkviolet */
{ 0x25832862, 0xffff1493 }, /* deeppink */
{ 0xfcad568f, 0xff00bfff }, /* deepskyblue */
{ 0x634c8b67, 0xff696969 }, /* dimgrey */
{ 0x634c8aeb, 0xff696969 }, /* dimgray */
{ 0x45c1ce55, 0xff1e90ff }, /* dodgerblue */
{ 0xef19e3cb, 0xffb22222 }, /* firebrick */
{ 0xb852b195, 0xfffffaf0 }, /* floralwhite */
{ 0xd086fd06, 0xff228b22 }, /* forestgreen */
{ 0xe106b6d7, 0xffff00ff }, /* fuchsia */
{ 0x7880d61e, 0xffdcdcdc }, /* gainsboro */
{ 0x2018a2fa, 0xfff8f8ff }, /* ghostwhite */
{ 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 */
{ 0x41cc4377, 0xff7cfc00 }, /* lawngreen */
{ 0x195756f0, 0xfffffacd }, /* lemonchiffon */
{ 0x28e4ea70, 0xffadd8e6 }, /* lightblue */
{ 0xf3c7ccdb, 0xfff08080 }, /* lightcoral */
{ 0x28e58d39, 0xffe0ffff }, /* lightcyan */
{ 0x21234e3c, 0xfffafad2 }, /* lightgoldenrodyellow */
{ 0xf40157ad, 0xff90ee90 }, /* lightgreen */
{ 0x28e744f5, 0xffd3d3d3 }, /* lightgrey */
{ 0x28e74479, 0xffd3d3d3 }, /* lightgray */
{ 0x28eb3b8c, 0xffffb6c1 }, /* lightpink */
{ 0x9fb78304, 0xffffa07a }, /* lightsalmon */
{ 0x50632b2a, 0xff20b2aa }, /* lightseagreen */
{ 0x68fb7b25, 0xff87cefa }, /* lightskyblue */
{ 0xa8a35ba2, 0xff778899 }, /* lightslategrey */
{ 0xa8a35b26, 0xff778899 }, /* lightslategray */
{ 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 */
{ 0xd9447d59, 0xffffe4b5 }, /* moccasin */
{ 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 */
{ 0xf381f607, 0xff663399 }, /* rebeccapurple */
{ 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 */
{ 0x44ab377c, 0xff708090 }, /* slategray */
{ 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 auto hash = (uint32) colourName.trim().toLowerCase().hashCode();
for (auto entry : presets)
if (entry.stringHash == hash)
return Colour (entry.colour);
return defaultColour;
}
} // namespace juce
@@ -0,0 +1,200 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/**
Contains a set of predefined named colours (mostly standard HTML colours)
@see Colour
@tags{Graphics}
*/
namespace juce::Colours
{
const Colour transparentBlack { 0 };
const Colour transparentWhite { 0x00ffffff };
const Colour aliceblue { 0xfff0f8ff };
const Colour antiquewhite { 0xfffaebd7 };
const Colour aqua { 0xff00ffff };
const Colour aquamarine { 0xff7fffd4 };
const Colour azure { 0xfff0ffff };
const Colour beige { 0xfff5f5dc };
const Colour bisque { 0xffffe4c4 };
const Colour black { 0xff000000 };
const Colour blanchedalmond { 0xffffebcd };
const Colour blue { 0xff0000ff };
const Colour blueviolet { 0xff8a2be2 };
const Colour brown { 0xffa52a2a };
const Colour burlywood { 0xffdeb887 };
const Colour cadetblue { 0xff5f9ea0 };
const Colour chartreuse { 0xff7fff00 };
const Colour chocolate { 0xffd2691e };
const Colour coral { 0xffff7f50 };
const Colour cornflowerblue { 0xff6495ed };
const Colour cornsilk { 0xfffff8dc };
const Colour crimson { 0xffdc143c };
const Colour cyan { 0xff00ffff };
const Colour darkblue { 0xff00008b };
const Colour darkcyan { 0xff008b8b };
const Colour darkgoldenrod { 0xffb8860b };
const Colour darkgrey { 0xff555555 };
const Colour darkgreen { 0xff006400 };
const Colour darkkhaki { 0xffbdb76b };
const Colour darkmagenta { 0xff8b008b };
const Colour darkolivegreen { 0xff556b2f };
const Colour darkorange { 0xffff8c00 };
const Colour darkorchid { 0xff9932cc };
const Colour darkred { 0xff8b0000 };
const Colour darksalmon { 0xffe9967a };
const Colour darkseagreen { 0xff8fbc8f };
const Colour darkslateblue { 0xff483d8b };
const Colour darkslategrey { 0xff2f4f4f };
const Colour darkturquoise { 0xff00ced1 };
const Colour darkviolet { 0xff9400d3 };
const Colour deeppink { 0xffff1493 };
const Colour deepskyblue { 0xff00bfff };
const Colour dimgrey { 0xff696969 };
const Colour dodgerblue { 0xff1e90ff };
const Colour firebrick { 0xffb22222 };
const Colour floralwhite { 0xfffffaf0 };
const Colour forestgreen { 0xff228b22 };
const Colour fuchsia { 0xffff00ff };
const Colour gainsboro { 0xffdcdcdc };
const Colour ghostwhite { 0xfff8f8ff };
const Colour gold { 0xffffd700 };
const Colour goldenrod { 0xffdaa520 };
const Colour grey { 0xff808080 };
const Colour green { 0xff008000 };
const Colour greenyellow { 0xffadff2f };
const Colour honeydew { 0xfff0fff0 };
const Colour hotpink { 0xffff69b4 };
const Colour indianred { 0xffcd5c5c };
const Colour indigo { 0xff4b0082 };
const Colour ivory { 0xfffffff0 };
const Colour khaki { 0xfff0e68c };
const Colour lavender { 0xffe6e6fa };
const Colour lavenderblush { 0xfffff0f5 };
const Colour lawngreen { 0xff7cfc00 };
const Colour lemonchiffon { 0xfffffacd };
const Colour lightblue { 0xffadd8e6 };
const Colour lightcoral { 0xfff08080 };
const Colour lightcyan { 0xffe0ffff };
const Colour lightgoldenrodyellow { 0xfffafad2 };
const Colour lightgreen { 0xff90ee90 };
const Colour lightgrey { 0xffd3d3d3 };
const Colour lightpink { 0xffffb6c1 };
const Colour lightsalmon { 0xffffa07a };
const Colour lightseagreen { 0xff20b2aa };
const Colour lightskyblue { 0xff87cefa };
const Colour lightslategrey { 0xff778899 };
const Colour lightsteelblue { 0xffb0c4de };
const Colour lightyellow { 0xffffffe0 };
const Colour lime { 0xff00ff00 };
const Colour limegreen { 0xff32cd32 };
const Colour linen { 0xfffaf0e6 };
const Colour magenta { 0xffff00ff };
const Colour maroon { 0xff800000 };
const Colour mediumaquamarine { 0xff66cdaa };
const Colour mediumblue { 0xff0000cd };
const Colour mediumorchid { 0xffba55d3 };
const Colour mediumpurple { 0xff9370db };
const Colour mediumseagreen { 0xff3cb371 };
const Colour mediumslateblue { 0xff7b68ee };
const Colour mediumspringgreen { 0xff00fa9a };
const Colour mediumturquoise { 0xff48d1cc };
const Colour mediumvioletred { 0xffc71585 };
const Colour midnightblue { 0xff191970 };
const Colour mintcream { 0xfff5fffa };
const Colour mistyrose { 0xffffe4e1 };
const Colour moccasin { 0xffffe4b5 };
const Colour navajowhite { 0xffffdead };
const Colour navy { 0xff000080 };
const Colour oldlace { 0xfffdf5e6 };
const Colour olive { 0xff808000 };
const Colour olivedrab { 0xff6b8e23 };
const Colour orange { 0xffffa500 };
const Colour orangered { 0xffff4500 };
const Colour orchid { 0xffda70d6 };
const Colour palegoldenrod { 0xffeee8aa };
const Colour palegreen { 0xff98fb98 };
const Colour paleturquoise { 0xffafeeee };
const Colour palevioletred { 0xffdb7093 };
const Colour papayawhip { 0xffffefd5 };
const Colour peachpuff { 0xffffdab9 };
const Colour peru { 0xffcd853f };
const Colour pink { 0xffffc0cb };
const Colour plum { 0xffdda0dd };
const Colour powderblue { 0xffb0e0e6 };
const Colour purple { 0xff800080 };
const Colour rebeccapurple { 0xff663399 };
const Colour red { 0xffff0000 };
const Colour rosybrown { 0xffbc8f8f };
const Colour royalblue { 0xff4169e1 };
const Colour saddlebrown { 0xff8b4513 };
const Colour salmon { 0xfffa8072 };
const Colour sandybrown { 0xfff4a460 };
const Colour seagreen { 0xff2e8b57 };
const Colour seashell { 0xfffff5ee };
const Colour sienna { 0xffa0522d };
const Colour silver { 0xffc0c0c0 };
const Colour skyblue { 0xff87ceeb };
const Colour slateblue { 0xff6a5acd };
const Colour slategrey { 0xff708090 };
const Colour snow { 0xfffffafa };
const Colour springgreen { 0xff00ff7f };
const Colour steelblue { 0xff4682b4 };
const Colour tan { 0xffd2b48c };
const Colour teal { 0xff008080 };
const Colour thistle { 0xffd8bfd8 };
const Colour tomato { 0xffff6347 };
const Colour turquoise { 0xff40e0d0 };
const Colour violet { 0xffee82ee };
const Colour wheat { 0xfff5deb3 };
const Colour white { 0xffffffff };
const Colour whitesmoke { 0xfff5f5f5 };
const Colour yellow { 0xffffff00 };
const Colour yellowgreen { 0xff9acd32 };
/** Attempts to look up a string in the list of known colour names, and return
the appropriate colour.
A non-case-sensitive search is made of the list of predefined colours, and
if a match is found, that colour is returned. If no match is found, the
colour passed in as the defaultColour parameter is returned.
*/
JUCE_API Colour findColourForName (const String& colourName,
Colour defaultColour);
} // namespace juce::Colours
@@ -0,0 +1,166 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
FillType::FillType() noexcept
: colour (0xff000000)
{
}
FillType::FillType (Colour c) noexcept
: colour (c)
{
}
FillType::FillType (const ColourGradient& g)
: colour (0xff000000), gradient (new ColourGradient (g))
{
}
FillType::FillType (ColourGradient&& g)
: colour (0xff000000), gradient (new ColourGradient (std::move (g)))
{
}
FillType::FillType (const Image& im, const AffineTransform& t) noexcept
: colour (0xff000000), image (im), transform (t)
{
}
FillType::FillType (const FillType& other)
: colour (other.colour),
gradient (createCopyIfNotNull (other.gradient.get())),
image (other.image),
transform (other.transform)
{
}
FillType& FillType::operator= (const FillType& other)
{
if (this != &other)
{
colour = other.colour;
gradient.reset (createCopyIfNotNull (other.gradient.get()));
image = other.image;
transform = other.transform;
}
return *this;
}
FillType::FillType (FillType&& other) noexcept
: colour (other.colour),
gradient (std::move (other.gradient)),
image (std::move (other.image)),
transform (other.transform)
{
}
FillType& FillType::operator= (FillType&& other) noexcept
{
jassert (this != &other); // hopefully the compiler should make this situation impossible!
colour = other.colour;
gradient = std::move (other.gradient);
image = std::move (other.image);
transform = other.transform;
return *this;
}
FillType::~FillType() noexcept
{
}
bool FillType::operator== (const FillType& other) const
{
return colour == other.colour && image == other.image
&& transform == other.transform
&& (gradient == other.gradient
|| (gradient != nullptr && other.gradient != nullptr && *gradient == *other.gradient));
}
bool FillType::operator!= (const FillType& other) const
{
return ! operator== (other);
}
void FillType::setColour (Colour newColour) noexcept
{
gradient.reset();
image = {};
colour = newColour;
}
void FillType::setGradient (const ColourGradient& newGradient)
{
if (gradient != nullptr)
{
*gradient = newGradient;
}
else
{
image = {};
gradient.reset (new ColourGradient (newGradient));
colour = Colours::black;
}
}
void FillType::setTiledImage (const Image& newImage, const AffineTransform& newTransform) noexcept
{
gradient.reset();
image = newImage;
transform = newTransform;
colour = Colours::black;
}
void FillType::setOpacity (const float newOpacity) noexcept
{
colour = colour.withAlpha (newOpacity);
}
bool FillType::isInvisible() const noexcept
{
return colour.isTransparent() || (gradient != nullptr && gradient->isInvisible());
}
FillType FillType::transformed (const AffineTransform& t) const
{
FillType f (*this);
f.transform = f.transform.followedBy (t);
return f;
}
} // namespace juce
@@ -0,0 +1,165 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a colour or fill pattern to use for rendering paths.
This is used by the Graphics and DrawablePath classes as a way to encapsulate
a brush type. It can either be a solid colour, a gradient, or a tiled image.
@see Graphics::setFillType, DrawablePath::setFill
@tags{Graphics}
*/
class JUCE_API FillType final
{
public:
//==============================================================================
/** Creates a default fill type, of solid black. */
FillType() noexcept;
/** Creates a fill type of a solid colour.
@see setColour
*/
FillType (Colour colour) noexcept;
/** Creates a gradient fill type.
@see setGradient
*/
FillType (const ColourGradient& gradient);
/** Creates a gradient fill type.
@see setGradient
*/
FillType (ColourGradient&& gradient);
/** Creates a tiled image fill type. The transform allows you to set the scaling, offset
and rotation of the pattern.
@see setTiledImage
*/
FillType (const Image& image, const AffineTransform& transform) noexcept;
/** Creates a copy of another FillType. */
FillType (const FillType&);
/** Makes a copy of another FillType. */
FillType& operator= (const FillType&);
/** Move constructor */
FillType (FillType&&) noexcept;
/** Move assignment operator */
FillType& operator= (FillType&&) noexcept;
/** Destructor. */
~FillType() noexcept;
//==============================================================================
/** Returns true if this is a solid colour fill, and not a gradient or image. */
bool isColour() const noexcept { return gradient == nullptr && image.isNull(); }
/** Returns true if this is a gradient fill. */
bool isGradient() const noexcept { return gradient != nullptr; }
/** Returns true if this is a tiled image pattern fill. */
bool isTiledImage() const noexcept { return image.isValid(); }
/** Turns this object into a solid colour fill.
If the object was an image or gradient, those fields will no longer be valid. */
void setColour (Colour newColour) noexcept;
/** Turns this object into a gradient fill. */
void setGradient (const ColourGradient& newGradient);
/** Turns this object into a tiled image fill type. The transform allows you to set
the scaling, offset and rotation of the pattern.
*/
void setTiledImage (const Image& image, const AffineTransform& transform) noexcept;
/** Changes the opacity that should be used.
If the fill is a solid colour, this just changes the opacity of that colour. For
gradients and image tiles, it changes the opacity that will be used for them.
*/
void setOpacity (float newOpacity) noexcept;
/** Returns the current opacity to be applied to the colour, gradient, or image.
@see setOpacity
*/
float getOpacity() const noexcept { return colour.getFloatAlpha(); }
/** Returns true if this fill type is completely transparent. */
bool isInvisible() const noexcept;
/** Returns a copy of this fill, adding the specified transform applied to the
existing transform.
*/
FillType transformed (const AffineTransform& transform) const;
//==============================================================================
/** The solid colour being used.
If the fill type is not a solid colour, the alpha channel of this colour indicates
the opacity that should be used for the fill, and the RGB channels are ignored.
*/
Colour colour;
/** Returns the gradient that should be used for filling.
This will be nullptr if the object is some other type of fill.
If a gradient is active, the overall opacity with which it should be applied
is indicated by the alpha channel of the colour variable.
*/
std::unique_ptr<ColourGradient> gradient;
/** The image that should be used for tiling.
If an image fill is active, the overall opacity with which it should be applied
is indicated by the alpha channel of the colour variable.
*/
Image image;
/** The transform that should be applied to the image or gradient that's being drawn. */
AffineTransform transform;
//==============================================================================
bool operator== (const FillType&) const;
bool operator!= (const FillType&) const;
private:
JUCE_LEAK_DETECTOR (FillType)
};
} // namespace juce
@@ -0,0 +1,749 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
#if JUCE_MSVC
#pragma pack (push, 1)
#endif
class PixelRGB;
class PixelAlpha;
inline uint32 maskPixelComponents (uint32 x) noexcept
{
return (x >> 8) & 0x00ff00ff;
}
inline uint32 clampPixelComponents (uint32 x) noexcept
{
return (x | (0x01000100 - maskPixelComponents (x))) & 0x00ff00ff;
}
//==============================================================================
/**
Represents a 32-bit INTERNAL pixel with premultiplied alpha, and can perform compositing
operations with it.
This is used internally by the imaging classes.
@see PixelRGB
@tags{Graphics}
*/
class JUCE_API PixelARGB
{
public:
/** Creates a pixel without defining its colour. */
PixelARGB() noexcept = default;
PixelARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept
{
components.b = b;
components.g = g;
components.r = r;
components.a = a;
}
//==============================================================================
/** Returns a uint32 which represents the pixel in a platform dependent format. */
forcedinline uint32 getNativeARGB() const noexcept { return internal; }
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
((alpha << 24) | (red << 16) | (green << 8) | blue). */
forcedinline uint32 getInARGBMaskOrder() const noexcept
{
#if JUCE_ANDROID
return (uint32) ((components.a << 24) | (components.r << 16) | (components.g << 8) | (components.b << 0));
#else
return getNativeARGB();
#endif
}
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
inline uint32 getInARGBMemoryOrder() const noexcept
{
#if JUCE_BIG_ENDIAN
return getInARGBMaskOrder();
#else
return (uint32) ((components.b << 24) | (components.g << 16) | (components.r << 8) | components.a);
#endif
}
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent. */
forcedinline uint32 getEvenBytes() const noexcept { return 0x00ff00ff & internal; }
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent. */
forcedinline uint32 getOddBytes() const noexcept { return 0x00ff00ff & (internal >> 8); }
//==============================================================================
forcedinline uint8 getAlpha() const noexcept { return components.a; }
forcedinline uint8 getRed() const noexcept { return components.r; }
forcedinline uint8 getGreen() const noexcept { return components.g; }
forcedinline uint8 getBlue() const noexcept { return components.b; }
//==============================================================================
/** Copies another pixel colour over this one.
This doesn't blend it - this colour is simply replaced by the other one.
*/
template <class Pixel>
forcedinline void set (const Pixel& src) noexcept
{
internal = src.getNativeARGB();
}
//==============================================================================
/** Sets the pixel's colour from individual components. */
void setARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept
{
components.b = b;
components.g = g;
components.r = r;
components.a = a;
}
//==============================================================================
/** Blends another pixel onto this one.
This takes into account the opacity of the pixel being overlaid, and blends
it accordingly.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src) noexcept
{
auto rb = src.getEvenBytes();
auto ag = src.getOddBytes();
const auto alpha = 0x100 - (ag >> 16);
rb += maskPixelComponents (getEvenBytes() * alpha);
ag += maskPixelComponents (getOddBytes() * alpha);
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8);
}
/** Blends another pixel onto this one.
This takes into account the opacity of the pixel being overlaid, and blends
it accordingly.
*/
forcedinline void blend (PixelRGB src) noexcept;
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
being used, so this can blend semi-transparently from a PixelRGB argument.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
{
auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes());
auto ag = maskPixelComponents (extraAlpha * src.getOddBytes());
const auto alpha = 0x100 - (ag >> 16);
rb += maskPixelComponents (getEvenBytes() * alpha);
ag += maskPixelComponents (getOddBytes() * alpha);
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8);
}
/** Blends another pixel with this one, creating a colour that is somewhere
between the two, as specified by the amount.
*/
template <class Pixel>
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
{
auto dEvenBytes = getEvenBytes();
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8);
dEvenBytes &= 0x00ff00ff;
auto dOddBytes = getOddBytes();
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8);
dOddBytes &= 0x00ff00ff;
dOddBytes <<= 8;
dOddBytes |= dEvenBytes;
internal = dOddBytes;
}
//==============================================================================
/** Replaces the colour's alpha value with another one. */
forcedinline void setAlpha (uint8 newAlpha) noexcept
{
components.a = newAlpha;
}
/** Multiplies the colour's alpha value with another one. */
forcedinline void multiplyAlpha (int multiplier) noexcept
{
// increment alpha by 1, so that if multiplier == 255 (full alpha),
// this function will not change the values.
++multiplier;
internal = ((((uint32) multiplier) * getOddBytes()) & 0xff00ff00)
| (((((uint32) multiplier) * getEvenBytes()) >> 8) & 0x00ff00ff);
}
forcedinline void multiplyAlpha (float multiplier) noexcept
{
multiplyAlpha ((int) (multiplier * 255.0f));
}
inline PixelARGB getUnpremultiplied() const noexcept
{
PixelARGB p (internal);
p.unpremultiply();
return p;
}
/** Premultiplies the pixel's RGB values by its alpha. */
forcedinline void premultiply() noexcept
{
const auto alpha = components.a;
if (alpha < 0xff)
{
if (alpha == 0)
{
components.b = 0;
components.g = 0;
components.r = 0;
}
else
{
components.b = (uint8) ((components.b * alpha + 0x7f) >> 8);
components.g = (uint8) ((components.g * alpha + 0x7f) >> 8);
components.r = (uint8) ((components.r * alpha + 0x7f) >> 8);
}
}
}
/** Unpremultiplies the pixel's RGB values. */
forcedinline void unpremultiply() noexcept
{
const auto alpha = components.a;
if (alpha < 0xff)
{
if (alpha == 0)
{
components.b = 0;
components.g = 0;
components.r = 0;
}
else
{
components.b = (uint8) jmin ((uint32) 0xffu, (components.b * 0xffu) / alpha);
components.g = (uint8) jmin ((uint32) 0xffu, (components.g * 0xffu) / alpha);
components.r = (uint8) jmin ((uint32) 0xffu, (components.r * 0xffu) / alpha);
}
}
}
forcedinline void desaturate() noexcept
{
if (components.a < 0xff && components.a > 0)
{
const auto newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a));
components.r = components.g = components.b
= (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8);
}
else
{
components.r = components.g = components.b
= (uint8) (((int) components.r + (int) components.g + (int) components.b) / 3);
}
}
//==============================================================================
/** The indexes of the different components in the byte layout of this type of colour. */
#if JUCE_ANDROID
#if JUCE_BIG_ENDIAN
enum { indexA = 0, indexR = 3, indexG = 2, indexB = 1 };
#else
enum { indexA = 3, indexR = 0, indexG = 1, indexB = 2 };
#endif
#else
#if JUCE_BIG_ENDIAN
enum { indexA = 0, indexR = 1, indexG = 2, indexB = 3 };
#else
enum { indexA = 3, indexR = 2, indexG = 1, indexB = 0 };
#endif
#endif
private:
//==============================================================================
PixelARGB (uint32 internalValue) noexcept
: internal (internalValue)
{
}
//==============================================================================
struct Components
{
#if JUCE_ANDROID
#if JUCE_BIG_ENDIAN
uint8 a, b, g, r;
#else
uint8 r, g, b, a;
#endif
#else
#if JUCE_BIG_ENDIAN
uint8 a, r, g, b;
#else
uint8 b, g, r, a;
#endif
#endif
} JUCE_PACKED;
union
{
uint32 internal;
Components components;
};
}
#ifndef DOXYGEN
JUCE_PACKED
#endif
;
//==============================================================================
/**
Represents a 24-bit RGB pixel, and can perform compositing operations on it.
This is used internally by the imaging classes.
@see PixelARGB
@tags{Graphics}
*/
class JUCE_API PixelRGB
{
public:
/** Creates a pixel without defining its colour. */
PixelRGB() noexcept = default;
//==============================================================================
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible
with the native format of a PixelARGB.
@see PixelARGB::getNativeARGB */
forcedinline uint32 getNativeARGB() const noexcept
{
#if JUCE_ANDROID
return (uint32) ((0xffu << 24) | r | ((uint32) g << 8) | ((uint32) b << 16));
#else
return (uint32) ((0xffu << 24) | b | ((uint32) g << 8) | ((uint32) r << 16));
#endif
}
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
((alpha << 24) | (red << 16) | (green << 8) | blue). */
forcedinline uint32 getInARGBMaskOrder() const noexcept
{
#if JUCE_ANDROID
return (uint32) ((0xffu << 24) | b | ((uint32) g << 8) | ((uint32) r << 16));
#else
return getNativeARGB();
#endif
}
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
inline uint32 getInARGBMemoryOrder() const noexcept
{
#if JUCE_BIG_ENDIAN
return getInARGBMaskOrder();
#else
return (uint32) ((b << 24) | (g << 16) | (r << 8) | 0xff);
#endif
}
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent but compatible with the
return value of getEvenBytes of the PixelARGB class.
@see PixelARGB::getEvenBytes */
forcedinline uint32 getEvenBytes() const noexcept
{
#if JUCE_ANDROID
return (uint32) (r | (b << 16));
#else
return (uint32) (b | (r << 16));
#endif
}
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent but compatible with the
return value of getOddBytes of the PixelARGB class.
@see PixelARGB::getOddBytes */
forcedinline uint32 getOddBytes() const noexcept { return (uint32) 0xff0000 | g; }
//==============================================================================
forcedinline uint8 getAlpha() const noexcept { return 0xff; }
forcedinline uint8 getRed() const noexcept { return r; }
forcedinline uint8 getGreen() const noexcept { return g; }
forcedinline uint8 getBlue() const noexcept { return b; }
//==============================================================================
/** Copies another pixel colour over this one.
This doesn't blend it - this colour is simply replaced by the other one.
Because PixelRGB has no alpha channel, any alpha value in the source pixel
is thrown away.
*/
template <class Pixel>
forcedinline void set (const Pixel& src) noexcept
{
b = src.getBlue();
g = src.getGreen();
r = src.getRed();
}
/** Sets the pixel's colour from individual components. */
void setARGB (uint8, uint8 red, uint8 green, uint8 blue) noexcept
{
r = red;
g = green;
b = blue;
}
//==============================================================================
/** Blends another pixel onto this one.
This takes into account the opacity of the pixel being overlaid, and blends
it accordingly.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src) noexcept
{
const auto alpha = (uint32) (0x100 - src.getAlpha());
// getEvenBytes returns 0x00rr00bb on non-android
const auto rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha));
// getOddBytes returns 0x00aa00gg on non-android
const auto ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8));
g = (uint8) (ag & 0xff);
#if JUCE_ANDROID
b = (uint8) (rb >> 16);
r = (uint8) (rb & 0xff);
#else
r = (uint8) (rb >> 16);
b = (uint8) (rb & 0xff);
#endif
}
forcedinline void blend (PixelRGB src) noexcept
{
set (src);
}
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
being used, so this can blend semi-transparently from a PixelRGB argument.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
{
auto ag = maskPixelComponents (extraAlpha * src.getOddBytes());
auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes());
const auto alpha = 0x100 - (ag >> 16);
ag = clampPixelComponents (ag + (g * alpha >> 8));
rb = clampPixelComponents (rb + maskPixelComponents (getEvenBytes() * alpha));
g = (uint8) (ag & 0xff);
#if JUCE_ANDROID
b = (uint8) (rb >> 16);
r = (uint8) (rb & 0xff);
#else
r = (uint8) (rb >> 16);
b = (uint8) (rb & 0xff);
#endif
}
/** Blends another pixel with this one, creating a colour that is somewhere
between the two, as specified by the amount.
*/
template <class Pixel>
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
{
auto dEvenBytes = getEvenBytes();
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8);
auto dOddBytes = getOddBytes();
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8);
g = (uint8) (dOddBytes & 0xff); // dOddBytes = 0x00aa00gg
#if JUCE_ANDROID
r = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00bb00rr
b = (uint8) (dEvenBytes >> 16);
#else
b = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00rr00bb
r = (uint8) (dEvenBytes >> 16);
#endif
}
//==============================================================================
/** This method is included for compatibility with the PixelARGB class. */
forcedinline void setAlpha (uint8) noexcept {}
/** Multiplies the colour's alpha value with another one. */
forcedinline void multiplyAlpha (int) noexcept {}
/** Multiplies the colour's alpha value with another one. */
forcedinline void multiplyAlpha (float) noexcept {}
/** Premultiplies the pixel's RGB values by its alpha. */
forcedinline void premultiply() noexcept {}
/** Unpremultiplies the pixel's RGB values. */
forcedinline void unpremultiply() noexcept {}
forcedinline void desaturate() noexcept
{
r = g = b = (uint8) (((int) r + (int) g + (int) b) / 3);
}
//==============================================================================
/** The indexes of the different components in the byte layout of this type of colour. */
#if JUCE_MAC || JUCE_IOS
enum { indexR = 0, indexG = 1, indexB = 2 };
#else
enum { indexR = 2, indexG = 1, indexB = 0 };
#endif
private:
//==============================================================================
PixelRGB (uint32 internal) noexcept
{
#if JUCE_ANDROID
b = (uint8) (internal >> 16);
g = (uint8) (internal >> 8);
r = (uint8) (internal);
#else
r = (uint8) (internal >> 16);
g = (uint8) (internal >> 8);
b = (uint8) (internal);
#endif
}
//==============================================================================
#if JUCE_MAC || JUCE_IOS
uint8 r, g, b;
#else
uint8 b, g, r;
#endif
}
#ifndef DOXYGEN
JUCE_PACKED
#endif
;
forcedinline void PixelARGB::blend (PixelRGB src) noexcept
{
set (src);
}
//==============================================================================
/**
Represents an 8-bit single-channel pixel, and can perform compositing operations on it.
This is used internally by the imaging classes.
@see PixelARGB, PixelRGB
@tags{Graphics}
*/
class JUCE_API PixelAlpha
{
public:
/** Creates a pixel without defining its colour. */
PixelAlpha() noexcept = default;
//==============================================================================
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible
with the native format of a PixelARGB.
@see PixelARGB::getNativeARGB */
forcedinline uint32 getNativeARGB() const noexcept { return (uint32) ((a << 24) | (a << 16) | (a << 8) | a); }
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
((alpha << 24) | (red << 16) | (green << 8) | blue). */
forcedinline uint32 getInARGBMaskOrder() const noexcept { return getNativeARGB(); }
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
inline uint32 getInARGBMemoryOrder() const noexcept { return getNativeARGB(); }
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent but compatible with the
return value of getEvenBytes of the PixelARGB class.
@see PixelARGB::getEvenBytes */
forcedinline uint32 getEvenBytes() const noexcept { return (uint32) ((a << 16) | a); }
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
operations. The exact channels which are returned is platform dependent but compatible with the
return value of getOddBytes of the PixelARGB class.
@see PixelARGB::getOddBytes */
forcedinline uint32 getOddBytes() const noexcept { return (uint32) ((a << 16) | a); }
//==============================================================================
forcedinline uint8 getAlpha() const noexcept { return a; }
forcedinline uint8 getRed() const noexcept { return 0; }
forcedinline uint8 getGreen() const noexcept { return 0; }
forcedinline uint8 getBlue() const noexcept { return 0; }
//==============================================================================
/** Copies another pixel colour over this one.
This doesn't blend it - this colour is simply replaced by the other one.
*/
template <class Pixel>
forcedinline void set (const Pixel& src) noexcept
{
a = src.getAlpha();
}
/** Sets the pixel's colour from individual components. */
forcedinline void setARGB (uint8 a_, uint8, uint8, uint8) noexcept
{
a = a_;
}
//==============================================================================
/** Blends another pixel onto this one.
This takes into account the opacity of the pixel being overlaid, and blends
it accordingly.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src) noexcept
{
const auto srcA = src.getAlpha();
a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA);
}
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
being used, so this can blend semi-transparently from a PixelRGB argument.
*/
template <class Pixel>
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
{
++extraAlpha;
const auto srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8);
a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha);
}
/** Blends another pixel with this one, creating a colour that is somewhere
between the two, as specified by the amount.
*/
template <class Pixel>
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
{
a += (uint8) (((src.getAlpha() - a) * amount) >> 8);
}
//==============================================================================
/** Replaces the colour's alpha value with another one. */
forcedinline void setAlpha (uint8 newAlpha) noexcept
{
a = newAlpha;
}
/** Multiplies the colour's alpha value with another one. */
forcedinline void multiplyAlpha (int multiplier) noexcept
{
++multiplier;
a = (uint8) ((a * multiplier) >> 8);
}
forcedinline void multiplyAlpha (float multiplier) noexcept
{
a = (uint8) (a * multiplier);
}
/** Premultiplies the pixel's RGB values by its alpha. */
forcedinline void premultiply() noexcept {}
/** Unpremultiplies the pixel's RGB values. */
forcedinline void unpremultiply() noexcept {}
forcedinline void desaturate() noexcept {}
//==============================================================================
/** The indexes of the different components in the byte layout of this type of colour. */
enum { indexA = 0 };
private:
//==============================================================================
PixelAlpha (uint32 internal) noexcept
: a ((uint8) (internal >> 24)) { }
//==============================================================================
uint8 a;
}
#ifndef DOXYGEN
JUCE_PACKED
#endif
;
#if JUCE_MSVC
#pragma pack (pop)
#endif
} // namespace juce
@@ -0,0 +1,946 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
static auto operator< (const Font& a, const Font& b)
{
return GraphicsFontHelpers::compareFont (a, b);
}
template <typename T>
static auto operator< (const Rectangle<T>& a, const Rectangle<T>& b)
{
const auto tie = [] (auto& t) { return std::make_tuple (t.getX(), t.getY(), t.getWidth(), t.getHeight()); };
return tie (a) < tie (b);
}
static auto operator< (const Justification& a, const Justification& b)
{
return a.getFlags() < b.getFlags();
}
//==============================================================================
namespace
{
template <typename ArrangementArgs>
class GlyphArrangementCache final : public DeletedAtShutdown
{
public:
GlyphArrangementCache() = default;
~GlyphArrangementCache() override
{
clearSingletonInstance();
}
template <typename ConfigureArrangement>
[[nodiscard]] auto get (ArrangementArgs&& args, ConfigureArrangement&& configureArrangement)
{
const ScopedTryLock stl (lock);
return stl.isLocked() ? cache.get (std::forward<ArrangementArgs> (args), std::forward<ConfigureArrangement> (configureArrangement))
: configureArrangement (args);
}
JUCE_DECLARE_SINGLETON_INLINE (GlyphArrangementCache<ArrangementArgs>, false)
private:
LruCache<ArrangementArgs, GlyphArrangement> cache;
CriticalSection lock;
};
//==============================================================================
template <typename Type>
Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept
{
#if JUCE_DEBUG
constexpr int maxVal = 0x3fffffff;
jassertquiet ((int) x >= -maxVal && (int) x <= maxVal
&& (int) y >= -maxVal && (int) y <= maxVal
&& (int) w >= 0 && (int) w <= maxVal
&& (int) h >= 0 && (int) h <= maxVal);
#endif
return { x, y, w, h };
}
}
//==============================================================================
Graphics::Graphics (const Image& imageToDrawOnto)
: contextHolder (imageToDrawOnto.createLowLevelContext()),
context (*contextHolder)
{
jassert (imageToDrawOnto.isValid()); // Can't draw into a null image!
}
Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept
: context (internalContext)
{
}
//==============================================================================
void Graphics::resetToDefaultState()
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::resetToDefaultState, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
context.setFill (FillType());
context.setFont (FontOptions{}.withMetricsKind (TypefaceMetricsKind::legacy));
context.setInterpolationQuality (Graphics::mediumResamplingQuality);
}
bool Graphics::isVectorDevice() const
{
return context.isVectorDevice();
}
bool Graphics::reduceClipRegion (Rectangle<int> area)
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangle, etw::graphicsKeyword, context.getFrameId(), area)
saveStateIfPending();
return context.clipToRectangle (area);
}
bool Graphics::reduceClipRegion (int x, int y, int w, int h)
{
return reduceClipRegion (coordsToRectangle (x, y, w, h));
}
bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion)
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangleList, etw::graphicsKeyword, context.getFrameId(), clipRegion)
saveStateIfPending();
return context.clipToRectangleList (clipRegion);
}
bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionPath, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
context.clipToPath (path, transform);
return ! context.isClipEmpty();
}
bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionImage, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
context.clipToImageAlpha (image, transform);
return ! context.isClipEmpty();
}
void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude)
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::excludeClipRegion, etw::graphicsKeyword, context.getFrameId(), rectangleToExclude);
saveStateIfPending();
context.excludeClipRectangle (rectangleToExclude);
}
bool Graphics::isClipEmpty() const
{
return context.isClipEmpty();
}
Rectangle<int> Graphics::getClipBounds() const
{
return context.getClipBounds();
}
void Graphics::saveState()
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
saveStatePending = true;
}
void Graphics::restoreState()
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::restoreState, etw::graphicsKeyword, context.getFrameId());
if (saveStatePending)
saveStatePending = false;
else
context.restoreState();
}
void Graphics::saveStateIfPending()
{
if (saveStatePending)
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId());
saveStatePending = false;
context.saveState();
}
}
void Graphics::setOrigin (Point<int> newOrigin)
{
saveStateIfPending();
context.setOrigin (newOrigin);
}
void Graphics::setOrigin (int x, int y)
{
setOrigin ({ x, y });
}
void Graphics::addTransform (const AffineTransform& transform)
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::addTransform, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
context.addTransform (transform);
}
bool Graphics::clipRegionIntersects (Rectangle<int> area) const
{
return context.clipRegionIntersects (area);
}
void Graphics::beginTransparencyLayer (float layerOpacity)
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::beginTransparencyLayer, etw::graphicsKeyword, context.getFrameId());
saveStateIfPending();
context.beginTransparencyLayer (layerOpacity);
}
void Graphics::endTransparencyLayer()
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endTransparencyLayer, etw::graphicsKeyword, context.getFrameId());
context.endTransparencyLayer();
}
//==============================================================================
void Graphics::setColour (Colour newColour)
{
saveStateIfPending();
context.setFill (newColour);
}
void Graphics::setOpacity (float newOpacity)
{
saveStateIfPending();
context.setOpacity (newOpacity);
}
void Graphics::setGradientFill (const ColourGradient& gradient)
{
setFillType (gradient);
}
void Graphics::setGradientFill (ColourGradient&& gradient)
{
setFillType (std::move (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)
{
setFont (context.getFont().withHeight (newFontHeight));
}
Font Graphics::getCurrentFont() const
{
return context.getFont();
}
//==============================================================================
void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
Justification justification) const
{
if (text.isEmpty())
return;
// Don't pass any vertical placement flags to this method - they'll be ignored.
jassert (justification.getOnlyVerticalFlags() == 0);
auto flags = justification.getOnlyHorizontalFlags();
if (flags == Justification::right && startX < context.getClipBounds().getX())
return;
if (flags == Justification::left && startX > context.getClipBounds().getRight())
return;
struct ArrangementArgs
{
auto tie() const noexcept { return std::tie (font, text); }
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
const Font font;
const String text;
};
auto configureArrangement = [] (const ArrangementArgs& args)
{
GlyphArrangement arrangement;
arrangement.addLineOfText (args.font, args.text, 0.0f, 0.0f);
return arrangement;
};
using Cache = GlyphArrangementCache<ArrangementArgs>;
ArrangementArgs args { context.getFont(), text };
const auto arrangement = Cache::getInstance()->get (std::move (args), std::move (configureArrangement));
const auto transform = std::invoke ([&]
{
const auto t = AffineTransform::translation ((float) startX,
(float) baselineY);
if (flags == Justification::left)
return t;
auto w = arrangement.getBoundingBox (0, -1, true).getWidth();
if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
w /= 2.0f;
return t.followedBy (AffineTransform::translation (-w, 0));
});
arrangement.draw (*this, transform);
}
void Graphics::drawMultiLineText (const String& text, const int startX,
const int baselineY, const int maximumLineWidth,
Justification justification, const float leading) const
{
if (text.isEmpty() || startX >= context.getClipBounds().getRight())
return;
struct ArrangementArgs
{
auto tie() const noexcept { return std::tie (font, text, maximumLineWidth, justification, leading); }
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
const Font font;
const String text;
const int maximumLineWidth;
const Justification justification;
const float leading;
};
auto configureArrangement = [] (const ArrangementArgs& args)
{
GlyphArrangement arrangement;
arrangement.addJustifiedText (args.font, args.text,
0.0f, 0.0f, (float) args.maximumLineWidth,
args.justification, args.leading);
return arrangement;
};
ArrangementArgs args { context.getFont(),
text,
maximumLineWidth,
justification,
leading };
using Cache = GlyphArrangementCache<ArrangementArgs>;
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
.draw (*this, AffineTransform::translation ((float) startX,
(float) baselineY));
}
void Graphics::drawText (const String& text, Rectangle<float> area,
Justification justificationType, bool useEllipsesIfTooBig) const
{
if (text.isEmpty() || ! context.clipRegionIntersects (area.getSmallestIntegerContainer()))
return;
struct ArrangementArgs
{
auto tie() const noexcept { return std::tie (font, text, width, height, justificationType, useEllipsesIfTooBig); }
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
const Font font;
const String text;
const float width;
const float height;
const Justification justificationType;
const bool useEllipsesIfTooBig;
};
auto configureArrangement = [] (const ArrangementArgs& args)
{
GlyphArrangement arrangement;
arrangement.addCurtailedLineOfText (args.font, args.text, 0.0f, 0.0f,
args.width, args.useEllipsesIfTooBig);
arrangement.justifyGlyphs (0, arrangement.getNumGlyphs(),
0.0f, 0.0f,
args.width, args.height,
args.justificationType);
return arrangement;
};
ArrangementArgs args { context.getFont(),
text,
area.getWidth(),
area.getHeight(),
justificationType,
useEllipsesIfTooBig };
using Cache = GlyphArrangementCache<ArrangementArgs>;
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
.draw (*this, AffineTransform::translation (area.getX(), area.getY()));
}
void Graphics::drawText (const String& text, Rectangle<int> area,
Justification justificationType, bool useEllipsesIfTooBig) const
{
drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig);
}
void Graphics::drawText (const String& text, int x, int y, int width, int height,
Justification justificationType, const bool useEllipsesIfTooBig) const
{
drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig);
}
void Graphics::drawFittedText (const String& text, Rectangle<int> area,
Justification justification,
const int maximumNumberOfLines,
const float minimumHorizontalScale) const
{
if (text.isEmpty() || area.isEmpty() || ! context.clipRegionIntersects (area))
return;
struct ArrangementArgs
{
auto tie() const noexcept { return std::tie (font, text, width, height, justification, maximumNumberOfLines, minimumHorizontalScale); }
bool operator< (const ArrangementArgs& other) const noexcept { return tie() < other.tie(); }
const Font font;
const String text;
const float width;
const float height;
const Justification justification;
const int maximumNumberOfLines;
const float minimumHorizontalScale;
};
auto configureArrangement = [] (const ArrangementArgs& args)
{
GlyphArrangement arrangement;
arrangement.addFittedText (args.font, args.text,
0.0f, 0.0f,
args.width, args.height,
args.justification,
args.maximumNumberOfLines,
args.minimumHorizontalScale);
return arrangement;
};
ArrangementArgs args { context.getFont(),
text,
(float) area.getWidth(),
(float) area.getHeight(),
justification,
maximumNumberOfLines,
minimumHorizontalScale };
using Cache = GlyphArrangementCache<ArrangementArgs>;
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
.draw (*this, AffineTransform::translation ((float) area.getX(),
(float) area.getY()));
}
void Graphics::drawFittedText (const String& text, int x, int y, int width, int height,
Justification justification,
const int maximumNumberOfLines,
const float minimumHorizontalScale) const
{
drawFittedText (text, coordsToRectangle (x, y, width, height),
justification, maximumNumberOfLines, minimumHorizontalScale);
}
//==============================================================================
void Graphics::fillRect (Rectangle<int> r) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r)
context.fillRect (r, false);
}
void Graphics::fillRect (Rectangle<float> r) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r)
context.fillRect (r);
}
void Graphics::fillRect (int x, int y, int width, int height) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height }))
context.fillRect (coordsToRectangle (x, y, width, height), false);
}
void Graphics::fillRect (float x, float y, float width, float height) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height }))
fillRect (coordsToRectangle (x, y, width, height));
}
void Graphics::fillRectList (const RectangleList<float>& rectangles) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rectangles)
context.fillRectList (rectangles);
}
void Graphics::fillRectList (const RectangleList<int>& rects) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rects)
RectangleList<float> converted;
for (const auto& r : rects)
converted.add (r.toFloat());
context.fillRectList (converted);
}
void Graphics::fillAll() const
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId())
context.fillAll();
}
void Graphics::fillAll (Colour colourToUse) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId())
if (! colourToUse.isTransparent())
{
context.saveState();
context.setFill (colourToUse);
context.fillAll();
context.restoreState();
}
}
//==============================================================================
void Graphics::fillPath (const Path& path) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId());
if (! (context.isClipEmpty() || path.isEmpty()))
context.fillPath (path, AffineTransform());
}
void Graphics::fillPath (const Path& path, const AffineTransform& transform) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId())
if (! (context.isClipEmpty() || path.isEmpty()))
context.fillPath (path, transform);
}
void Graphics::strokePath (const Path& path,
const PathStrokeType& strokeType,
const AffineTransform& transform) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::strokePath, etw::graphicsKeyword, context.getFrameId())
if (! (context.isClipEmpty() || path.isEmpty()))
context.strokePath (path, strokeType, transform);
}
//==============================================================================
void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const
{
drawRect (coordsToRectangle (x, y, width, height), lineThickness);
}
void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const
{
drawRect (coordsToRectangle (x, y, width, height), lineThickness);
}
void Graphics::drawRect (Rectangle<int> r, int lineThickness) const
{
drawRect (r.toFloat(), (float) lineThickness);
}
void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const
{
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::drawRect, etw::graphicsKeyword, context.getFrameId(), r)
jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f);
context.drawRect (r, lineThickness);
}
//==============================================================================
void Graphics::fillEllipse (Rectangle<float> area) const
{
context.fillEllipse (area);
}
void Graphics::fillEllipse (float x, float y, float w, float h) const
{
fillEllipse (coordsToRectangle (x, y, w, h));
}
void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const
{
drawEllipse (coordsToRectangle (x, y, width, height), lineThickness);
}
void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const
{
context.drawEllipse (area, lineThickness);
}
void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const
{
fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize);
}
void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const
{
context.fillRoundedRectangle (r, cornerSize);
}
void Graphics::drawRoundedRectangle (float x, float y, float width, float height,
float cornerSize, float lineThickness) const
{
drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness);
}
void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const
{
context.drawRoundedRectangle (r, cornerSize, lineThickness);
}
void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const
{
Path p;
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
fillPath (p);
}
void Graphics::fillCheckerBoard (Rectangle<float> area, float checkWidth, float checkHeight,
Colour colour1, Colour colour2) const
{
jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
if (checkWidth > 0 && checkHeight > 0)
{
context.saveState();
if (colour1 == colour2)
{
context.setFill (colour1);
context.fillRect (area);
}
else
{
auto clipped = context.getClipBounds().getIntersection (area.getSmallestIntegerContainer());
if (! clipped.isEmpty())
{
const int checkNumX = (int) (((float) clipped.getX() - area.getX()) / checkWidth);
const int checkNumY = (int) (((float) clipped.getY() - area.getY()) / checkHeight);
const float startX = area.getX() + (float) checkNumX * checkWidth;
const float startY = area.getY() + (float) checkNumY * checkHeight;
const float right = (float) clipped.getRight();
const float bottom = (float) clipped.getBottom();
for (int i = 0; i < 2; ++i)
{
int cy = i;
RectangleList<float> checks;
for (float y = startY; y < bottom; y += checkHeight)
for (float x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2.0f)
checks.addWithoutMerging ({ x, y, checkWidth, checkHeight });
checks.clipTo (area);
context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
context.fillRectList (checks);
}
}
}
context.restoreState();
}
}
//==============================================================================
void Graphics::drawVerticalLine (const int x, float top, float bottom) const
{
if (top < bottom)
context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top));
}
void Graphics::drawHorizontalLine (const int y, float left, float right) const
{
if (left < right)
context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f));
}
void Graphics::drawLine (Line<float> line) const
{
context.drawLine (line);
}
void Graphics::drawLine (float x1, float y1, float x2, float y2) const
{
context.drawLine (Line<float> (x1, y1, x2, y2));
}
void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const
{
drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
}
void Graphics::drawLine (Line<float> line, const float lineThickness) const
{
context.drawLineWithThickness (line, lineThickness);
}
void Graphics::drawDashedLine (Line<float> line, const float* dashLengths,
int numDashLengths, float lineThickness, int n) const
{
jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
const double totalLen = delta.getDistanceFromOrigin();
if (totalLen >= 0.1)
{
const double onePixAlpha = 1.0 / totalLen;
for (double alpha = 0.0; alpha < 1.0;)
{
jassert (dashLengths[n] > 0); // can't have zero-length dashes!
const double lastAlpha = alpha;
alpha += dashLengths [n] * onePixAlpha;
n = (n + 1) % numDashLengths;
if ((n & 1) != 0)
{
const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
line.getStart() + (delta * jmin (1.0, alpha)).toFloat());
if (! approximatelyEqual (lineThickness, 1.0f))
drawLine (segment, lineThickness);
else
context.drawLine (segment);
}
}
}
}
//==============================================================================
void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
{
saveStateIfPending();
context.setInterpolationQuality (newQuality);
}
//==============================================================================
void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const
{
drawImageTransformed (imageToDraw,
AffineTransform::translation ((float) x, (float) y),
fillAlphaChannel);
}
void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
{
if (imageToDraw.isValid())
drawImageTransformed (imageToDraw,
placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea),
fillAlphaChannelWithCurrentBrush);
}
void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh,
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
{
drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(),
placementWithinTarget, fillAlphaChannelWithCurrentBrush);
}
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
{
if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh)))
drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)),
AffineTransform::scale ((float) dw / (float) sw, (float) dh / (float) sh)
.translated ((float) dx, (float) dy),
fillAlphaChannelWithCurrentBrush);
}
void Graphics::drawImageTransformed (const Image& imageToDraw,
const AffineTransform& transform,
const bool fillAlphaChannelWithCurrentBrush) const
{
if (imageToDraw.isValid() && ! context.isClipEmpty())
{
if (fillAlphaChannelWithCurrentBrush)
{
context.saveState();
context.clipToImageAlpha (imageToDraw, transform);
fillAll();
context.restoreState();
}
else
{
context.drawImage (imageToDraw, transform);
}
}
}
//==============================================================================
Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g)
{
context.saveState();
}
Graphics::ScopedSaveState::~ScopedSaveState()
{
context.restoreState();
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class GraphicsTests : public UnitTest
{
public:
GraphicsTests() : UnitTest ("Graphics", UnitTestCategories::graphics) {}
void runTest() override
{
beginTest ("Render image subsection");
{
const SoftwareImageType softwareImageType;
const NativeImageType nativeImageType;
const ImageType* types[] { &softwareImageType, &nativeImageType };
for (auto* sourceType : types)
for (auto* targetType : types)
renderImageSubsection (*sourceType, *targetType);
}
}
private:
void renderImageSubsection (const ImageType& sourceType, const ImageType& targetType)
{
const auto sourceColour = Colours::cyan;
const auto sourceOffset = 49;
const Image source { Image::ARGB, 50, 50, true, sourceType };
const Image target { Image::ARGB, 50, 50, true, targetType };
const auto subsection = source.getClippedImage (Rectangle { sourceOffset, sourceOffset, 1, 1 });
Image::BitmapData { subsection, Image::BitmapData::writeOnly }.setPixelColour (0, 0, sourceColour);
{
// Render the subsection image so that it fills 'target'
Graphics g { target };
// Use low resampling quality, because we want to avoid our pixel getting blurry when it's scaled up
g.setImageResamplingQuality (Graphics::lowResamplingQuality);
g.drawImage (subsection,
0, 0, target.getWidth(), target.getHeight(),
0, 0, 1, 1);
}
{
// Check that all pixels in 'target' match the bottom right pixel of 'source'
const Image::BitmapData bitmap { target, Image::BitmapData::readOnly };
int numFailures = 0;
for (auto y = 0; y < bitmap.height; ++y)
{
for (auto x = 0; x < bitmap.width; ++x)
{
const auto targetColour = bitmap.getPixelColour (x, y);
if (targetColour != sourceColour)
++numFailures;
}
}
expect (numFailures == 0);
}
}
};
static GraphicsTests graphicsTests;
#endif
} // namespace juce
@@ -0,0 +1,758 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A graphics context, used for drawing a component or image.
When a Component needs painting, a Graphics context is passed to its
Component::paint() method, and this you then call methods within this
object to actually draw the component's content.
A Graphics can also be created from an image, to allow drawing directly onto
that image.
@see Component::paint
@tags{Graphics}
*/
class JUCE_API Graphics final
{
public:
//==============================================================================
/** Creates a Graphics object to draw directly onto the given image.
The graphics object that is created will be set up to draw onto the image,
with the context's clipping area being the entire size of the image, and its
origin being the image's origin. To draw into a subsection of an image, use the
reduceClipRegion() and setOrigin() methods.
Obviously you shouldn't delete the image before this context is deleted.
*/
explicit Graphics (const Image& imageToDrawOnto);
//==============================================================================
/** Changes the current drawing colour.
This sets the colour that will now be used for drawing operations - it also
sets the opacity to that of the colour passed-in.
If a brush is being used when this method is called, the brush will be deselected,
and any subsequent drawing will be done with a solid colour brush instead.
@see setOpacity
*/
void setColour (Colour newColour);
/** Changes the opacity to use with the current colour.
If a solid colour is being used for drawing, this changes its opacity
to this new value (i.e. it doesn't multiply the colour's opacity by this amount).
If a gradient is being used, this will have no effect on it.
A value of 0.0 is completely transparent, 1.0 is completely opaque.
*/
void setOpacity (float newOpacity);
/** Sets the context to use a gradient for its fill pattern. */
void setGradientFill (const ColourGradient& gradient);
/** Sets the context to use a gradient for its fill pattern. */
void setGradientFill (ColourGradient&& gradient);
/** Sets the context to use a tiled image pattern for filling.
Make sure that you don't delete this image while it's still being used by
this context!
*/
void setTiledImageFill (const Image& imageToUse,
int anchorX, int anchorY,
float opacity);
/** Changes the current fill settings.
@see setColour, setGradientFill, setTiledImageFill
*/
void setFillType (const FillType& newFill);
//==============================================================================
/** Changes the font to use for subsequent text-drawing functions.
@see drawSingleLineText, drawMultiLineText, drawText, drawFittedText
*/
void setFont (const Font& newFont);
/** Changes the size of the currently-selected font.
This is a convenient shortcut that changes the context's current font to a
different size. The typeface won't be changed.
@see Font
*/
void setFont (float newFontHeight);
/** Returns the currently selected font. */
Font getCurrentFont() const;
/** Draws a one-line text string.
This will use the current colour (or brush) to fill the text. The font is the last
one specified by setFont().
@param text the string to draw
@param startX the position to draw the left-hand edge of the text
@param baselineY the position of the text's baseline
@param justification the horizontal flags indicate which end of the text string is
anchored at the specified point.
@see drawMultiLineText, drawText, drawFittedText, GlyphArrangement::addLineOfText
*/
void drawSingleLineText (const String& text,
int startX, int baselineY,
Justification justification = Justification::left) const;
/** Draws text across multiple lines.
This will break the text onto a new line where there's a new-line or
carriage-return character, or at a word-boundary when the text becomes wider
than the size specified by the maximumLineWidth parameter. New-lines
will be vertically separated by the specified leading.
@see setFont, drawSingleLineText, drawFittedText, GlyphArrangement::addJustifiedText
*/
void drawMultiLineText (const String& text,
int startX, int baselineY,
int maximumLineWidth,
Justification justification = Justification::left,
float leading = 0.0f) const;
/** Draws a line of text within a specified rectangle.
The text will be positioned within the rectangle based on the justification
flags passed-in. If the string is too long to fit inside the rectangle, it will
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
flag is true).
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
*/
void drawText (const String& text,
int x, int y, int width, int height,
Justification justificationType,
bool useEllipsesIfTooBig = true) const;
/** Draws a line of text within a specified rectangle.
The text will be positioned within the rectangle based on the justification
flags passed-in. If the string is too long to fit inside the rectangle, it will
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
flag is true).
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
*/
void drawText (const String& text,
Rectangle<int> area,
Justification justificationType,
bool useEllipsesIfTooBig = true) const;
/** Draws a line of text within a specified rectangle.
The text will be positioned within the rectangle based on the justification
flags passed-in. If the string is too long to fit inside the rectangle, it will
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
flag is true).
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
*/
void drawText (const String& text,
Rectangle<float> area,
Justification justificationType,
bool useEllipsesIfTooBig = true) const;
/** Tries to draw a text string inside a given space.
This does its best to make the given text readable within the specified rectangle,
so it's useful for labelling things.
If the text is too big, it'll be squashed horizontally or broken over multiple lines
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
it'll cram as much as possible in there, and put some ellipsis at the end to show that
it's been truncated.
A Justification parameter lets you specify how the text is laid out within the rectangle,
both horizontally and vertically.
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
can set this value to 1.0f. Pass 0 if you want it to use a default value.
@see GlyphArrangement::addFittedText
*/
void drawFittedText (const String& text,
int x, int y, int width, int height,
Justification justificationFlags,
int maximumNumberOfLines,
float minimumHorizontalScale = 0.0f) const;
/** Tries to draw a text string inside a given space.
This does its best to make the given text readable within the specified rectangle,
so it's useful for labelling things.
If the text is too big, it'll be squashed horizontally or broken over multiple lines
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
it'll cram as much as possible in there, and put some ellipsis at the end to show that
it's been truncated.
A Justification parameter lets you specify how the text is laid out within the rectangle,
both horizontally and vertically.
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
can set this value to 1.0f. Pass 0 if you want it to use a default value.
@see GlyphArrangement::addFittedText
*/
void drawFittedText (const String& text,
Rectangle<int> area,
Justification justificationFlags,
int maximumNumberOfLines,
float minimumHorizontalScale = 0.0f) const;
//==============================================================================
/** Fills the context's entire clip region with the current colour or brush.
(See also the fillAll (Colour) method which is a quick way of filling
it with a given colour).
*/
void fillAll() const;
/** Fills the context's entire clip region with a given colour.
This leaves the context's current colour and brush unchanged, it just
uses the specified colour temporarily.
*/
void fillAll (Colour colourToUse) const;
//==============================================================================
/** Fills a rectangle with the current colour or brush.
@see drawRect, fillRoundedRectangle
*/
void fillRect (Rectangle<int> rectangle) const;
/** Fills a rectangle with the current colour or brush.
@see drawRect, fillRoundedRectangle
*/
void fillRect (Rectangle<float> rectangle) const;
/** Fills a rectangle with the current colour or brush.
@see drawRect, fillRoundedRectangle
*/
void fillRect (int x, int y, int width, int height) const;
/** Fills a rectangle with the current colour or brush.
@see drawRect, fillRoundedRectangle
*/
void fillRect (float x, float y, float width, float height) const;
/** Fills a set of rectangles using the current colour or brush.
If you have a lot of rectangles to draw, it may be more efficient
to create a RectangleList and use this method than to call fillRect()
multiple times.
*/
void fillRectList (const RectangleList<float>& rectangles) const;
/** Fills a set of rectangles using the current colour or brush.
If you have a lot of rectangles to draw, it may be more efficient
to create a RectangleList and use this method than to call fillRect()
multiple times.
*/
void fillRectList (const RectangleList<int>& rectangles) const;
/** Uses the current colour or brush to fill a rectangle with rounded corners.
@see drawRoundedRectangle, Path::addRoundedRectangle
*/
void fillRoundedRectangle (float x, float y, float width, float height,
float cornerSize) const;
/** Uses the current colour or brush to fill a rectangle with rounded corners.
@see drawRoundedRectangle, Path::addRoundedRectangle
*/
void fillRoundedRectangle (Rectangle<float> rectangle,
float cornerSize) const;
/** Fills a rectangle with a checkerboard pattern, alternating between two colours. */
void fillCheckerBoard (Rectangle<float> area,
float checkWidth, float checkHeight,
Colour colour1, Colour colour2) const;
/** Draws a rectangular outline, using the current colour or brush.
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
@see fillRect
*/
void drawRect (int x, int y, int width, int height, int lineThickness = 1) const;
/** Draws a rectangular outline, using the current colour or brush.
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
@see fillRect
*/
void drawRect (float x, float y, float width, float height, float lineThickness = 1.0f) const;
/** Draws a rectangular outline, using the current colour or brush.
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
@see fillRect
*/
void drawRect (Rectangle<int> rectangle, int lineThickness = 1) const;
/** Draws a rectangular outline, using the current colour or brush.
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
@see fillRect
*/
void drawRect (Rectangle<float> rectangle, float lineThickness = 1.0f) const;
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners.
@see fillRoundedRectangle, Path::addRoundedRectangle
*/
void drawRoundedRectangle (float x, float y, float width, float height,
float cornerSize, float lineThickness) const;
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners.
@see fillRoundedRectangle, Path::addRoundedRectangle
*/
void drawRoundedRectangle (Rectangle<float> rectangle,
float cornerSize, float lineThickness) const;
//==============================================================================
/** Fills an ellipse with the current colour or brush.
The ellipse is drawn to fit inside the given rectangle.
@see drawEllipse, Path::addEllipse
*/
void fillEllipse (float x, float y, float width, float height) const;
/** Fills an ellipse with the current colour or brush.
The ellipse is drawn to fit inside the given rectangle.
@see drawEllipse, Path::addEllipse
*/
void fillEllipse (Rectangle<float> area) const;
/** Draws an elliptical stroke using the current colour or brush.
@see fillEllipse, Path::addEllipse
*/
void drawEllipse (float x, float y, float width, float height,
float lineThickness) const;
/** Draws an elliptical stroke using the current colour or brush.
@see fillEllipse, Path::addEllipse
*/
void drawEllipse (Rectangle<float> area, float lineThickness) const;
//==============================================================================
/** Draws a line between two points.
The line is 1 pixel wide and drawn with the current colour or brush.
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
it's better to use fillRect() instead unless you really need an angled line.
*/
void drawLine (float startX, float startY, float endX, float endY) const;
/** Draws a line between two points with a given thickness.
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
it's better to use fillRect() instead unless you really need an angled line.
@see Path::addLineSegment
*/
void drawLine (float startX, float startY, float endX, float endY, float lineThickness) const;
/** Draws a line between two points.
The line is 1 pixel wide and drawn with the current colour or brush.
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
it's better to use fillRect() instead unless you really need an angled line.
*/
void drawLine (Line<float> line) const;
/** Draws a line between two points with a given thickness.
@see Path::addLineSegment
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
it's better to use fillRect() instead unless you really need an angled line.
*/
void drawLine (Line<float> line, float lineThickness) const;
/** Draws a dashed line using a custom set of dash-lengths.
@param line the line to draw
@param dashLengths a series of lengths to specify the on/off lengths - e.g.
{ 4, 5, 6, 7 } will draw a line of 4 pixels, skip 5 pixels,
draw 6 pixels, skip 7 pixels, and then repeat.
@param numDashLengths the number of elements in the array (this must be an even number).
@param lineThickness the thickness of the line to draw
@param dashIndexToStartFrom the index in the dash-length array to use for the first segment
@see PathStrokeType::createDashedStroke
*/
void drawDashedLine (Line<float> line,
const float* dashLengths, int numDashLengths,
float lineThickness = 1.0f,
int dashIndexToStartFrom = 0) const;
/** Draws a vertical line of pixels at a given x position.
The x position is an integer, but the top and bottom of the line can be sub-pixel
positions, and these will be anti-aliased if necessary.
The bottom parameter must be greater than or equal to the top parameter.
*/
void drawVerticalLine (int x, float top, float bottom) const;
/** Draws a horizontal line of pixels at a given y position.
The y position is an integer, but the left and right ends of the line can be sub-pixel
positions, and these will be anti-aliased if necessary.
The right parameter must be greater than or equal to the left parameter.
*/
void drawHorizontalLine (int y, float left, float right) const;
//==============================================================================
/** Fills a path using the currently selected colour or brush. */
void fillPath (const Path& path) const;
/** Fills a path using the currently selected colour or brush, and adds a transform. */
void fillPath (const Path& path, const AffineTransform& transform) const;
/** Draws a path's outline using the currently selected colour or brush. */
void strokePath (const Path& path,
const PathStrokeType& strokeType,
const AffineTransform& transform = {}) const;
/** Draws a line with an arrowhead at its end.
@param line the line to draw
@param lineThickness the thickness of the line
@param arrowheadWidth the width of the arrow head (perpendicular to the line)
@param arrowheadLength the length of the arrow head (along the length of the line)
*/
void drawArrow (Line<float> line,
float lineThickness,
float arrowheadWidth,
float arrowheadLength) const;
//==============================================================================
/** Types of rendering quality that can be specified when drawing images.
@see Graphics::setImageResamplingQuality
*/
enum ResamplingQuality
{
lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */
mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */
highResamplingQuality = 2, /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */
};
/** Changes the quality that will be used when resampling images.
By default a Graphics object will be set to mediumRenderingQuality.
@see Graphics::drawImage, Graphics::drawImageTransformed, Graphics::drawImageWithin
*/
void setImageResamplingQuality (ResamplingQuality newQuality);
/** Draws an image.
This will draw the whole of an image, positioning its top-left corner at the
given coordinates, and keeping its size the same. This is the simplest image
drawing method - the others give more control over the scaling and clipping
of the images.
Images are composited using the context's current opacity, so if you
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
(or setColour() with an opaque colour) before drawing images.
*/
void drawImageAt (const Image& imageToDraw, int topLeftX, int topLeftY,
bool fillAlphaChannelWithCurrentBrush = false) const;
/** Draws part of an image, rescaling it to fit in a given target region.
The specified area of the source image is rescaled and drawn to fill the
specified destination rectangle.
Images are composited using the context's current opacity, so if you
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
(or setColour() with an opaque colour) before drawing images.
@param imageToDraw the image to overlay
@param destX the left of the destination rectangle
@param destY the top of the destination rectangle
@param destWidth the width of the destination rectangle
@param destHeight the height of the destination rectangle
@param sourceX the left of the rectangle to copy from the source image
@param sourceY the top of the rectangle to copy from the source image
@param sourceWidth the width of the rectangle to copy from the source image
@param sourceHeight the height of the rectangle to copy from the source image
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the source image's pixels,
the source image's alpha channel is used as a mask with
which to fill the destination using the current colour
or brush. (If the source is has no alpha channel, then
it will just fill the target with a solid rectangle)
@see setImageResamplingQuality, drawImageAt, drawImageWithin, fillAlphaMap
*/
void drawImage (const Image& imageToDraw,
int destX, int destY, int destWidth, int destHeight,
int sourceX, int sourceY, int sourceWidth, int sourceHeight,
bool fillAlphaChannelWithCurrentBrush = false) const;
/** Draws an image, having applied an affine transform to it.
This lets you throw the image around in some wacky ways, rotate it, shear,
scale it, etc.
Images are composited using the context's current opacity, so if you
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
(or setColour() with an opaque colour) before drawing images.
If fillAlphaChannelWithCurrentBrush is set to true, then the image's RGB channels
are ignored and it is filled with the current brush, masked by its alpha channel.
If you want to render only a subsection of an image, use Image::getClippedImage() to
create the section that you need.
@see setImageResamplingQuality, drawImage
*/
void drawImageTransformed (const Image& imageToDraw,
const AffineTransform& transform,
bool fillAlphaChannelWithCurrentBrush = false) const;
/** Draws an image to fit within a designated rectangle.
@param imageToDraw the source image to draw
@param targetArea the target rectangle to fit it into
@param placementWithinTarget this specifies how the image should be positioned
within the target rectangle - see the RectanglePlacement
class for more details about this.
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its
alpha channel will be used as a mask with which to
draw with the current brush or colour. This is
similar to fillAlphaMap(), and see also drawImage()
@see drawImage, drawImageTransformed, drawImageAt, RectanglePlacement
*/
void drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
RectanglePlacement placementWithinTarget = RectanglePlacement::stretchToFit,
bool fillAlphaChannelWithCurrentBrush = false) const;
/** Draws an image to fit within a designated rectangle.
If the image is too big or too small for the space, it will be rescaled
to fit as nicely as it can do without affecting its aspect ratio. It will
then be placed within the target rectangle according to the justification flags
specified.
@param imageToDraw the source image to draw
@param destX top-left of the target rectangle to fit it into
@param destY top-left of the target rectangle to fit it into
@param destWidth size of the target rectangle to fit the image into
@param destHeight size of the target rectangle to fit the image into
@param placementWithinTarget this specifies how the image should be positioned
within the target rectangle - see the RectanglePlacement
class for more details about this.
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its
alpha channel will be used as a mask with which to
draw with the current brush or colour. This is
similar to fillAlphaMap(), and see also drawImage()
@see setImageResamplingQuality, drawImage, drawImageTransformed, drawImageAt, RectanglePlacement
*/
void drawImageWithin (const Image& imageToDraw,
int destX, int destY, int destWidth, int destHeight,
RectanglePlacement placementWithinTarget,
bool fillAlphaChannelWithCurrentBrush = false) const;
//==============================================================================
/** Returns the position of the bounding box for the current clipping region.
@see clipRegionIntersects
*/
Rectangle<int> getClipBounds() const;
/** Checks whether a rectangle overlaps the context's clipping region.
If this returns false, no part of the given area can be drawn onto, so this
method can be used to optimise a component's paint() method, by letting it
avoid drawing complex objects that aren't within the region being repainted.
*/
bool clipRegionIntersects (Rectangle<int> area) const;
/** Intersects the current clipping region with another region.
@returns true if the resulting clipping region is non-zero in size
@see setOrigin, clipRegionIntersects
*/
bool reduceClipRegion (int x, int y, int width, int height);
/** Intersects the current clipping region with another region.
@returns true if the resulting clipping region is non-zero in size
@see setOrigin, clipRegionIntersects
*/
bool reduceClipRegion (Rectangle<int> area);
/** Intersects the current clipping region with a rectangle list region.
@returns true if the resulting clipping region is non-zero in size
@see setOrigin, clipRegionIntersects
*/
bool reduceClipRegion (const RectangleList<int>& clipRegion);
/** Intersects the current clipping region with a path.
@returns true if the resulting clipping region is non-zero in size
@see reduceClipRegion
*/
bool reduceClipRegion (const Path& path, const AffineTransform& transform = AffineTransform());
/** Intersects the current clipping region with an image's alpha-channel.
The current clipping path is intersected with the area covered by this image's
alpha-channel, after the image has been transformed by the specified matrix.
@param image the image whose alpha-channel should be used. If the image doesn't
have an alpha-channel, it is treated as entirely opaque.
@param transform a matrix to apply to the image
@returns true if the resulting clipping region is non-zero in size
@see reduceClipRegion
*/
bool reduceClipRegion (const Image& image, const AffineTransform& transform);
/** Excludes a rectangle to stop it being drawn into. */
void excludeClipRegion (Rectangle<int> rectangleToExclude);
/** Returns true if no drawing can be done because the clip region is zero. */
bool isClipEmpty() const;
//==============================================================================
/** Saves the current graphics state on an internal stack.
To restore the state, use restoreState().
@see ScopedSaveState
*/
void saveState();
/** Restores a graphics state that was previously saved with saveState().
@see ScopedSaveState
*/
void restoreState();
/** Uses RAII to save and restore the state of a graphics context.
On construction, this calls Graphics::saveState(), and on destruction it calls
Graphics::restoreState() on the Graphics object that you supply.
*/
class ScopedSaveState
{
public:
ScopedSaveState (Graphics&);
~ScopedSaveState();
private:
Graphics& context;
JUCE_DECLARE_NON_COPYABLE (ScopedSaveState)
};
//==============================================================================
/** Begins rendering to an off-screen bitmap which will later be flattened onto the current
context with the given opacity.
The context uses an internal stack of temporary image layers to do this. When you've
finished drawing to the layer, call endTransparencyLayer() to complete the operation and
composite the finished layer. Every call to beginTransparencyLayer() MUST be matched
by a corresponding call to endTransparencyLayer()!
This call also saves the current state, and endTransparencyLayer() restores it.
*/
void beginTransparencyLayer (float layerOpacity);
/** Completes a drawing operation to a temporary semi-transparent buffer.
See beginTransparencyLayer() for more details.
*/
void endTransparencyLayer();
/** Moves the position of the context's origin.
This changes the position that the context considers to be (0, 0) to
the specified position.
So if you call setOrigin with (100, 100), then the position that was previously
referred to as (100, 100) will subsequently be considered to be (0, 0).
@see reduceClipRegion, addTransform
*/
void setOrigin (Point<int> newOrigin);
/** Moves the position of the context's origin.
This changes the position that the context considers to be (0, 0) to
the specified position.
So if you call setOrigin (100, 100), then the position that was previously
referred to as (100, 100) will subsequently be considered to be (0, 0).
@see reduceClipRegion, addTransform
*/
void setOrigin (int newOriginX, int newOriginY);
/** Adds a transformation which will be performed on all the graphics operations that
the context subsequently performs.
After calling this, all the coordinates that are passed into the context will be
transformed by this matrix.
@see setOrigin
*/
void addTransform (const AffineTransform& transform);
/** Resets the current colour, brush, and font to default settings. */
void resetToDefaultState();
/** Returns true if this context is drawing to a vector-based device, such as a printer. */
bool isVectorDevice() const;
//==============================================================================
/** Create a graphics that draws with a given low-level renderer.
This method is intended for use only by people who know what they're doing.
Note that the LowLevelGraphicsContext will NOT be deleted by this object.
*/
Graphics (LowLevelGraphicsContext&) noexcept;
/** @internal */
LowLevelGraphicsContext& getInternalContext() const noexcept { return context; }
private:
//==============================================================================
std::unique_ptr<LowLevelGraphicsContext> contextHolder;
LowLevelGraphicsContext& context;
bool saveStatePending = false;
void saveStateIfPending();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics)
};
} // namespace juce
@@ -0,0 +1,186 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Interface class for graphics context objects, used internally by the Graphics class.
Users are not supposed to create instances of this class directly - do your drawing
via the Graphics object instead.
It's a base class for different types of graphics context, that may perform software-based
or OS-accelerated rendering.
E.g. the LowLevelGraphicsSoftwareRenderer renders onto an image in memory, but other
subclasses could render directly to a windows HDC, a Quartz context, or an OpenGL
context.
@tags{Graphics}
*/
class JUCE_API LowLevelGraphicsContext
{
protected:
//==============================================================================
LowLevelGraphicsContext() = default;
public:
virtual ~LowLevelGraphicsContext() = default;
/** Returns true if this device is vector-based, e.g. a printer. */
virtual bool isVectorDevice() const = 0;
//==============================================================================
/** Moves the origin to a new position.
The coordinates are relative to the current origin, and indicate the new position
of (0, 0).
*/
virtual void setOrigin (Point<int>) = 0;
virtual void addTransform (const AffineTransform&) = 0;
virtual float getPhysicalPixelScaleFactor() const = 0;
virtual bool clipToRectangle (const Rectangle<int>&) = 0;
virtual bool clipToRectangleList (const RectangleList<int>&) = 0;
virtual void excludeClipRectangle (const Rectangle<int>&) = 0;
virtual void clipToPath (const Path&, const AffineTransform&) = 0;
virtual void clipToImageAlpha (const Image&, const AffineTransform&) = 0;
virtual bool clipRegionIntersects (const Rectangle<int>&) = 0;
virtual Rectangle<int> getClipBounds() const = 0;
virtual bool isClipEmpty() const = 0;
virtual void saveState() = 0;
virtual void restoreState() = 0;
virtual void beginTransparencyLayer (float opacity) = 0;
virtual void endTransparencyLayer() = 0;
//==============================================================================
virtual void setFill (const FillType&) = 0;
virtual void setOpacity (float) = 0;
virtual void setInterpolationQuality (Graphics::ResamplingQuality) = 0;
//==============================================================================
virtual void fillAll() { fillRect (getClipBounds(), false); }
virtual void fillRect (const Rectangle<int>&, bool replaceExistingContents) = 0;
virtual void fillRect (const Rectangle<float>&) = 0;
virtual void fillRectList (const RectangleList<float>&) = 0;
virtual void fillPath (const Path&, const AffineTransform&) = 0;
virtual void drawRect (const Rectangle<float>& rect, float lineThickness)
{
auto r = rect;
RectangleList<float> rects;
rects.addWithoutMerging (r.removeFromTop (lineThickness));
rects.addWithoutMerging (r.removeFromBottom (lineThickness));
rects.addWithoutMerging (r.removeFromLeft (lineThickness));
rects.addWithoutMerging (r.removeFromRight (lineThickness));
fillRectList (rects);
}
virtual void strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform)
{
Path stroke;
strokeType.createStrokedPath (stroke, path, transform, getPhysicalPixelScaleFactor());
fillPath (stroke, {});
}
virtual void drawImage (const Image&, const AffineTransform&) = 0;
virtual void drawLine (const Line<float>&) = 0;
virtual void drawLineWithThickness (const Line<float>& line, float lineThickness)
{
Path p;
p.addLineSegment (line, lineThickness);
fillPath (p, {});
}
virtual void setFont (const Font&) = 0;
virtual const Font& getFont() = 0;
/** Uses the current font to draw the provided glyph numbers. */
virtual void drawGlyphs (Span<const uint16_t>,
Span<const Point<float>>,
const AffineTransform&) = 0;
virtual void drawRoundedRectangle (const Rectangle<float>& r, float cornerSize, float lineThickness)
{
Path p;
p.addRoundedRectangle (r, cornerSize);
strokePath (p, PathStrokeType (lineThickness), {});
}
virtual void fillRoundedRectangle (const Rectangle<float>& r, float cornerSize)
{
Path p;
p.addRoundedRectangle (r, cornerSize);
fillPath (p, {});
}
virtual void drawEllipse (const Rectangle<float>& area, float lineThickness)
{
Path p;
if (approximatelyEqual (area.getWidth(), area.getHeight()))
{
// For a circle, we can avoid having to generate a stroke
p.addEllipse (area.expanded (lineThickness * 0.5f));
p.addEllipse (area.reduced (lineThickness * 0.5f));
p.setUsingNonZeroWinding (false);
fillPath (p, {});
}
else
{
p.addEllipse (area);
strokePath (p, PathStrokeType (lineThickness), {});
}
}
virtual void fillEllipse (const Rectangle<float>& area)
{
Path p;
p.addEllipse (area);
fillPath (p, {});
}
/** Returns an integer that uniquely identifies the current frame.
Useful for debugging/logging.
*/
virtual uint64_t getFrameId() const = 0;
};
} // namespace juce
@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image)
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
(new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds()))
{
JUCE_TRACE_LOG_PAINT_CALL (etw::startGDIImage, getFrameId());
}
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point<int> origin,
const RectangleList<int>& initialClip)
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
(new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin))
{
JUCE_TRACE_EVENT_INT_RECT_LIST (etw::startGDIFrame, etw::softwareRendererKeyword, getFrameId(), initialClip);
}
LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer()
{
JUCE_TRACE_LOG_PAINT_CALL (etw::endGDIFrame, getFrameId());
}
} // namespace juce
@@ -0,0 +1,66 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A lowest-common-denominator implementation of LowLevelGraphicsContext that does all
its rendering in memory.
User code is not supposed to create instances of this class directly - do all your
rendering via the Graphics class instead.
@tags{Graphics}
*/
class JUCE_API LowLevelGraphicsSoftwareRenderer : public RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
{
public:
//==============================================================================
/** Creates a context to render into an image. */
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto);
/** Creates a context to render into a clipped subsection of an image. */
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, Point<int> origin,
const RectangleList<int>& initialClip);
/** Destructor. */
~LowLevelGraphicsSoftwareRenderer() override;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer)
};
} // namespace juce
@@ -0,0 +1,627 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform);
//==============================================================================
static constexpr auto maxWidthTolerance = 0.005f;
static int64 getNumLeadingWhitespaces (Span<const ShapedGlyph> glyphs)
{
const auto it = std::find_if_not (glyphs.begin(),
glyphs.end(),
[&] (const auto& g) { return g.isWhitespace(); });
return (int64) std::distance (glyphs.begin(), it);
}
static int64 getNumTrailingWhitespaces (Span<const ShapedGlyph> glyphs)
{
if (glyphs.empty())
return 0;
int64 trailingWhitespaces = 0;
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
++trailingWhitespaces;
return trailingWhitespaces;
}
struct NumWhitespaces
{
int64 total{}, leading{}, trailing{};
};
static NumWhitespaces getNumWhitespaces (Span<const ShapedGlyph> glyphs)
{
const auto total = std::count_if (glyphs.begin(),
glyphs.end(),
[] (const auto& g) { return g.isWhitespace(); });
return { total, getNumLeadingWhitespaces (glyphs), getNumTrailingWhitespaces (glyphs) };
}
struct LineLength
{
float total{}, withoutTrailingWhitespaces{};
};
static LineLength getMainAxisLineLength (Span<const ShapedGlyph> glyphs)
{
const auto total = std::accumulate (glyphs.begin(),
glyphs.end(),
0.0f,
[] (auto acc, const auto& g) { return acc + g.advance.getX(); });
auto trailingWhitespacesLength = 0.0f;
if (glyphs.empty())
return {};
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
trailingWhitespacesLength += it->advance.getX();
return { total, total - trailingWhitespacesLength };
}
static float getMainAxisLineLength (Span<const ShapedGlyph> glyphs, bool trailingWhitespacesShouldFit)
{
const auto lengths = getMainAxisLineLength (glyphs);
return trailingWhitespacesShouldFit ? lengths.total : lengths.withoutTrailingWhitespaces;
}
struct MainAxisLineAlignment
{
float anchor{}, extraWhitespaceAdvance{}, effectiveLineLength{};
Range<int64> stretchableWhitespaces;
};
static MainAxisLineAlignment getMainAxisLineAlignment (Justification justification,
Span<const ShapedGlyph> glyphs,
LineLength lineLength,
std::optional<float> maxWidthOpt,
std::optional<float> alignmentWidthOpt,
bool trailingWhitespacesShouldFit)
{
const auto effectiveLineLength = (trailingWhitespacesShouldFit ? lineLength.total
: lineLength.withoutTrailingWhitespaces);
const auto alignmentWidth = alignmentWidthOpt.value_or (maxWidthOpt.value_or (0.0f));
const auto tooLong = alignmentWidth + maxWidthTolerance < effectiveLineLength;
MainAxisLineAlignment result;
result.effectiveLineLength = effectiveLineLength;
// The alignment width opt is supporting the TextEditor use-case where all text remains visible
// with scrolling, even if longer than the alignment width. We don't need to worry about the
// front of and RTL text being visually truncated, because nothing is truncated.
if (tooLong && alignmentWidthOpt.has_value())
return result;
const auto mainAxisLineOffset = [&]
{
if (tooLong)
{
const auto approximateIsLeftToRight = [&]
{
if (glyphs.empty())
return true;
return glyphs.front().cluster <= glyphs.back().cluster;
}();
// We don't have to align LTR text, but we need to ensure that it's the logical back of
// RTL text that falls outside the bounds.
if (approximateIsLeftToRight)
return 0.0f;
return alignmentWidth - effectiveLineLength;
}
if (justification.testFlags (Justification::horizontallyCentred))
{
return (alignmentWidth - lineLength.withoutTrailingWhitespaces) / 2.0f;
}
if (justification.testFlags (Justification::right))
return alignmentWidth - effectiveLineLength;
return 0.0f;
}();
const auto numWhitespaces = getNumWhitespaces (glyphs);
const auto stretchableWhitespaces = [&]() -> Range<int64>
{
if (! justification.testFlags (Justification::horizontallyJustified) || tooLong)
return {};
return { numWhitespaces.leading, (int64) glyphs.size() - numWhitespaces.trailing };
}();
const auto extraWhitespaceAdvance = [&]
{
if (! justification.testFlags (Justification::horizontallyJustified) || tooLong)
return 0.0f;
const auto numWhitespacesBetweenWords = numWhitespaces.total
- numWhitespaces.leading
- numWhitespaces.trailing;
return numWhitespacesBetweenWords > 0 ? (alignmentWidth - effectiveLineLength) / (float) numWhitespacesBetweenWords
: 0.0f;
}();
return { mainAxisLineOffset, extraWhitespaceAdvance, effectiveLineLength, stretchableWhitespaces };
}
struct LineInfo
{
float lineHeight{}, maxAscent{};
MainAxisLineAlignment mainAxisLineAlignment;
};
static float getCrossAxisStartingAnchor (Justification justification,
Span<const LineInfo> lineInfos,
std::optional<float> height,
float leadingInHeight)
{
if (lineInfos.empty())
return 0.0f;
const auto minimumTop = lineInfos.front().maxAscent + lineInfos.front().lineHeight * leadingInHeight;
if (! height.has_value())
return minimumTop;
const auto textHeight = std::accumulate (lineInfos.begin(),
lineInfos.end(),
0.0f,
[] (auto acc, const auto info) { return acc + info.lineHeight; });
if (justification.testFlags (Justification::verticallyCentred))
return (*height - textHeight) / 2.0f + lineInfos.front().maxAscent;
if (justification.testFlags (Justification::bottom))
{
const auto bottomLeading = 0.5f * lineInfos.back().lineHeight * leadingInHeight;
return *height - textHeight - bottomLeading + lineInfos.front().maxAscent;
}
return minimumTop;
}
JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions& options)
: shapedText (*t)
{
const auto leading = options.getLeading() - 1.0f;
std::vector<LineInfo> lineInfos;
for (const auto [range, lineNumber] : shapedText.getLineNumbersForGlyphRanges())
{
// This is guaranteed by the RangedValues implementation. You can't assign a value to an
// empty range.
jassert (! range.isEmpty());
const auto fonts = shapedText.getResolvedFonts().getIntersectionsWith (range);
const auto lineHeight = std::accumulate (fonts.begin(),
fonts.end(),
0.0f,
[] (auto acc, const auto& rangedFont)
{ return std::max (acc, rangedFont.value.getHeight()); });
const auto maxAscent = std::accumulate (fonts.begin(),
fonts.end(),
0.0f,
[] (auto acc, const auto& rangedFont)
{ return std::max (acc, rangedFont.value.getAscent()); });
const auto glyphs = shapedText.getGlyphs (range);
const auto lineLength = getMainAxisLineLength (glyphs);
auto m = [&]
{
return getMainAxisLineAlignment (options.getJustification(),
glyphs,
lineLength,
options.getMaxWidth(),
options.getAlignmentWidth(),
options.getTrailingWhitespacesShouldFit());
}();
const auto containsHardBreak = shapedText.getCodepoint (range.getEnd() - 1) == 0xa
|| shapedText.getCodepoint (range.getStart()) == 0xa;
if (containsHardBreak || lineNumber == shapedText.getLineNumbersForGlyphRanges().back().value)
{
m.extraWhitespaceAdvance = {};
m.stretchableWhitespaces = {};
}
lineInfos.push_back ({ lineHeight, maxAscent, std::move (m) });
minimumRequiredWidthsForLine.push_back (options.getTrailingWhitespacesShouldFit() ? lineLength.total
: lineLength.withoutTrailingWhitespaces);
}
auto baseline = options.isBaselineAtZero() ? 0.0f
: getCrossAxisStartingAnchor (options.getJustification(),
lineInfos,
options.getHeight(),
leading);
detail::Ranges::Operations ops;
std::optional<float> top;
for (const auto [lineIndex, lineInfo] : enumerate (lineInfos))
{
const auto lineNumber = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineIndex);
const auto range = lineNumber.range;
const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent;
const auto nextLineTop = baseline + (1.0f + leading) * maxDescent + options.getAdditiveLineSpacing();
if (! top.has_value())
top = baseline - (1.0f + leading) * lineInfo.maxAscent;
lineMetricsForGlyphRange.set (range,
{ lineNumber.value,
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
lineInfo.maxAscent,
lineInfo.lineHeight - lineInfo.maxAscent,
lineInfo.mainAxisLineAlignment.effectiveLineLength
+ lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
*top,
nextLineTop },
ops,
MergeEqualItemsNo{});
whitespaceStretch.set (range, 0.0f, ops);
const auto stretchRange = lineInfo.mainAxisLineAlignment.stretchableWhitespaces + range.getStart();
whitespaceStretch.set (stretchRange,
lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
ops);
ops.clear();
const auto nextLineMaxAscent = lineIndex < (int) lineInfos.size() - 1 ? lineInfos[(size_t) lineIndex + 1].maxAscent : 0.0f;
baseline = nextLineTop + (1.0f + leading) * nextLineMaxAscent;
top = nextLineTop;
}
rangesToDraw.set ({ 0, (int64) shapedText.getGlyphs().size() }, DrawType::normal, ops);
//==============================================================================
// Everything above this line should work well given none of the lines were too
// long. When Options::getMaxNumLines() == 0 this is guaranteed by SimpleShapedText.
// The remaining logic below is for supporting
// GlyphArrangement::addFittedText() when the maximum number of lines is
// constrained.
if (lineMetricsForGlyphRange.isEmpty())
return;
const auto lastLineMetrics = lineMetricsForGlyphRange.back();
const auto lastLineGlyphRange = lastLineMetrics.range;
const auto lastLineGlyphs = shapedText.getGlyphs (lastLineGlyphRange);
const auto lastLineLengths = getMainAxisLineLength (lastLineGlyphs);
const auto effectiveLength = options.getTrailingWhitespacesShouldFit() ? lastLineLengths.total
: lastLineLengths.withoutTrailingWhitespaces;
if (! options.getMaxWidth().has_value()
|| effectiveLength <= *options.getMaxWidth() + maxWidthTolerance)
return;
const auto cutoffAtFront = lastLineMetrics.value.anchor.getX() < 0.0f - maxWidthTolerance;
const auto getLastLineVisibleRange = [&] (float ellipsisLength)
{
const auto r = [&]() -> Range<int64>
{
if (cutoffAtFront)
{
auto length = lastLineLengths.total;
for (auto it = lastLineGlyphs.begin(); it < lastLineGlyphs.end(); ++it)
{
length -= it->advance.getX();
if (! options.getMaxWidth().has_value()
|| *options.getMaxWidth() >= ellipsisLength + length)
{
return { (int64) std::distance (lastLineGlyphs.begin(), it) + 1,
(int64) lastLineGlyphs.size() };
}
}
}
else
{
auto length = lastLineLengths.total;
for (auto it = lastLineGlyphs.end() - 1; it >= lastLineGlyphs.begin(); --it)
{
length -= it->advance.getX();
if (! options.getMaxWidth().has_value()
|| *options.getMaxWidth() >= ellipsisLength + length)
{
return { 0, (int64) std::distance (lastLineGlyphs.begin(), it) };
}
}
}
return {};
}();
return r.movedToStartAt (r.getStart() + lastLineGlyphRange.getStart());
};
const auto lastLineVisibleRangeWithoutEllipsis = getLastLineVisibleRange (0.0f);
const auto eraseLastLineFromRangesToDraw = [&]
{
rangesToDraw.eraseFrom (lastLineGlyphRange.getStart(), ops);
ops.clear();
};
eraseLastLineFromRangesToDraw();
rangesToDraw.set (lastLineVisibleRangeWithoutEllipsis, DrawType::normal, ops);
ops.clear();
if (options.getEllipsis().isEmpty())
{
return;
}
//==============================================================================
// More logic supporting using ellipses
const auto fontForEllipsis = [&]
{
const auto lastLineFonts = shapedText.getResolvedFonts().getIntersectionsWith (lastLineGlyphRange);
if (cutoffAtFront)
return lastLineFonts.front().value;
return lastLineFonts.back().value;
}();
ellipsis.emplace (&options.getEllipsis(), ShapedTextOptions {}.withFont (fontForEllipsis));
const auto lastLineVisibleRange = getLastLineVisibleRange (getMainAxisLineLength (ellipsis->getGlyphs(),
options.getTrailingWhitespacesShouldFit()));
eraseLastLineFromRangesToDraw();
rangesToDraw.set (lastLineVisibleRange, DrawType::normal, ops);
ops.clear();
if (cutoffAtFront)
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getStart() - 1, 1), DrawType::ellipsis, ops);
else
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getEnd(), 1), DrawType::ellipsis, ops);
ops.clear();
const auto lineWithEllipsisGlyphs = [&]
{
std::vector<ShapedGlyph> result;
const auto pushEllipsisGlyphs = [&]
{
const auto& range = ellipsis->getGlyphs();
result.insert (result.begin(), range.begin(), range.end());
};
if (cutoffAtFront)
pushEllipsisGlyphs();
const auto& range = shapedText.getGlyphs (lastLineVisibleRange);
result.insert (result.end(), range.begin(), range.end());
if (! cutoffAtFront)
pushEllipsisGlyphs();
return result;
}();
const auto realign = [&]
{
return getMainAxisLineAlignment (options.getJustification(),
lineWithEllipsisGlyphs,
getMainAxisLineLength (lineWithEllipsisGlyphs),
options.getMaxWidth(),
options.getAlignmentWidth(),
options.getTrailingWhitespacesShouldFit());
}();
lastLineMetrics.value.anchor.setX (realign.anchor);
whitespaceStretch.set (lastLineGlyphRange, 0.0f, ops);
whitespaceStretch.set (realign.stretchableWhitespaces + lastLineVisibleRange.getStart(),
realign.extraWhitespaceAdvance, ops);
}
int64 JustifiedText::getGlyphIndexToTheRightOf (Point<float> p) const
{
auto lineIt = lineMetricsForGlyphRange.begin();
float lineTop = 0.0f;
while (lineIt != lineMetricsForGlyphRange.end())
{
const auto nextLineTop = lineIt->value.nextLineTop;
if (lineTop <= p.getY() && p.getY() < nextLineTop)
break;
lineTop = nextLineTop;
++lineIt;
}
if (lineIt == lineMetricsForGlyphRange.end())
return 0;
const auto glyphsInLine = shapedText.getGlyphs (lineIt->range);
auto glyphIndex = lineIt->range.getStart();
auto glyphX = lineIt->value.anchor.getX();
for (const auto& glyph : glyphsInLine)
{
if ( p.getX() < glyphX + glyph.advance.getX() / 2.0f
|| glyph.isNewline()
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.isWhitespace()))
{
break;
}
++glyphIndex;
glyphX += glyph.advance.getX();
}
return glyphIndex;
}
GlyphAnchorResult JustifiedText::getGlyphAnchor (int64 index) const
{
jassert (index >= 0);
if (lineMetricsForGlyphRange.isEmpty())
return {};
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (index)
.value_or (lineMetricsForGlyphRange.back());
const auto indexInLine = index - lineItem.range.getStart();
GlyphAnchorResult anchor { lineItem.value.anchor, lineItem.value.maxAscent, lineItem.value.maxDescent };
for (auto [i, glyph] : enumerate (shapedText.getGlyphs (lineItem.range), int64{}))
{
if (i == indexInLine)
{
anchor.anchor += glyph.offset;
return anchor;
}
anchor.anchor += glyph.advance;
}
return anchor;
}
RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) const
{
RectangleList<float> bounds;
if (lineMetricsForGlyphRange.isEmpty())
return bounds;
const auto getBounds = [&] (const LineMetrics& line, int64 lineStart, int64 boundsStart, int64 boundsEnd) -> Rectangle<float>
{
const auto glyphsBefore = shapedText.getGlyphs ({ lineStart, boundsStart });
const auto xStart = std::accumulate (glyphsBefore.begin(),
glyphsBefore.end(),
line.anchor.getX(),
[] (auto sum, auto glyph)
{
return sum + glyph.advance.getX();
});
const auto glyphs = shapedText.getGlyphs ({ boundsStart, boundsEnd });
const auto xEnd = std::accumulate (glyphs.begin(),
glyphs.end(),
xStart,
[&] (auto sum, auto glyph)
{
return sum + glyph.advance.getX();
});
return { { xStart, line.top }, { xEnd, line.nextLineTop } };
};
for (auto consumeFrom = glyphRange.getStart(); consumeFrom < glyphRange.getEnd();)
{
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (consumeFrom);
if (! lineItem.has_value())
break;
const auto consumeTo = std::min (glyphRange.getEnd(), lineItem->range.getEnd());
bounds.add (getBounds (lineItem->value, lineItem->range.getStart(), consumeFrom, consumeTo));
consumeFrom = consumeTo;
}
return bounds;
}
float JustifiedText::getHeight() const
{
if (lineMetricsForGlyphRange.isEmpty())
return 0.0f;
return lineMetricsForGlyphRange.back().value.nextLineTop;
}
void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform transform)
{
auto& context = g.getInternalContext();
context.saveState();
const ScopeGuard restoreGraphicsContext { [&context] { context.restoreState(); } };
text.accessTogetherWith ([&] (auto glyphs, auto positions, auto font, auto, auto)
{
if (context.getFont() != font)
context.setFont (font);
std::vector<uint16_t> glyphIds (glyphs.size());
std::transform (glyphs.begin(),
glyphs.end(),
glyphIds.begin(),
[] (auto& glyph) { return (uint16_t) glyph.glyphId; });
context.drawGlyphs (glyphIds, positions, transform);
});
}
} // namespace juce::detail
@@ -0,0 +1,233 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
template <size_t StartingAt, typename Tuple, size_t... Is>
constexpr auto partiallyUnpackImpl (Tuple&& tuple, std::index_sequence<Is...>)
{
return std::tie (std::get<StartingAt + Is> (tuple)...);
}
template <size_t StartingAt, size_t NumElems, typename Tuple>
constexpr auto partiallyUnpack (Tuple&& tuple)
{
return partiallyUnpackImpl<StartingAt> (std::forward<Tuple> (tuple), std::make_index_sequence<NumElems>{});
}
//==============================================================================
struct LineMetrics
{
int64 lineNumber;
Point<float> anchor;
float maxAscent;
float maxDescent;
/* Effective suggests the length of trailing whitespaces will be included or not depending on
ShapedTextOptions::getTrailingWhitespacesShouldFit().
*/
float effectiveLineLength;
/* These values seem redundant given the relation between the baseline, ascent and top, but
we want to ensure exactlyEquals (top, nextLineTop) for subsequent lines.
*/
float top;
/* This will be below the current line's visual bottom if non-default leading or additive
line spacing is used.
*/
float nextLineTop;
};
struct GlyphAnchorResult
{
Point<float> anchor;
float maxAscent{};
float maxDescent{};
};
//==============================================================================
class JustifiedText
{
private:
enum class DrawType
{
normal,
ellipsis
};
public:
JustifiedText (const SimpleShapedText* t, const ShapedTextOptions& options);
/* Provides access to the data stored in the ShapedText.
The provided callable will be called multiple times for "uniform glyph runs", for which all
callback parameters are the same.
Between each subsequent callback at least one of the provided parameters will be different.
The callbacks happen in visual order i.e. left to right, which is irrespective of the
underlying text's writing direction.
The callback parameters in order are:
- the glyphs
- the positions for each glyph in the previous parameter
- the Font with which these glyphs should be rendered
- the range in all glyphs this ShapedText object holds, that correspond to the current glyphs
- a line number which increases by one for each new line
- followed by any number of ValueType parameters for the supplied RangedValues<ValueType> arguments
*/
template <typename Callable, typename... RangedValues>
void accessTogetherWith (Callable&& callback, RangedValues&&... rangedValues) const
{
std::optional<int64> lastLine;
int64 lastGlyph = 0;
Point<float> anchor {};
for (const auto item : makeIntersectingRangedValues (&shapedText.getResolvedFonts(),
&lineMetricsForGlyphRange,
&rangesToDraw,
&whitespaceStretch,
(&rangedValues)...))
{
const auto& [range, font, lineMetrics, drawType, stretch] = partiallyUnpack<0, 5> (item);
const auto& rest = partiallyUnpack<5, std::tuple_size_v<decltype (item)> - 5> (item);
if (std::exchange (lastLine, lineMetrics.lineNumber) != lineMetrics.lineNumber)
anchor = lineMetrics.anchor;
if (range.getStart() != lastGlyph && drawType != DrawType::ellipsis)
{
detail::RangedValues<int8_t> glyphMask;
Ranges::Operations ops;
const auto firstGlyphInCurrentLine = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineMetrics.lineNumber)
.range
.getStart();
glyphMask.set ({ std::max (lastGlyph, firstGlyphInCurrentLine), range.getStart() }, 1, ops);
for (const auto [skippedRange, skippedStretch, _] : makeIntersectingRangedValues (&whitespaceStretch,
&glyphMask))
{
ignoreUnused (_);
for (const auto& skippedGlyph : shapedText.getGlyphs (skippedRange))
{
anchor += skippedGlyph.advance;
if (skippedGlyph.isWhitespace())
anchor.addXY (skippedStretch, 0.0f);
}
}
}
lastGlyph = range.getEnd();
const auto glyphs = [this, r = range, dt = drawType]() -> Span<const ShapedGlyph>
{
if (dt == DrawType::ellipsis)
return ellipsis->getGlyphs();
return shapedText.getGlyphs (r);
}();
std::vector<Point<float>> positions (glyphs.size());
std::transform (glyphs.begin(),
glyphs.end(),
positions.begin(),
[&anchor, &s = stretch] (auto& glyph)
{
auto result = anchor + glyph.offset;
anchor += glyph.advance;
if (glyph.isWhitespace())
anchor.addXY (s, 0.0f);
return result;
});
const auto callbackFont =
drawType == DrawType::ellipsis ? ellipsis->getResolvedFonts().front().value : font;
const auto callbackParameters =
std::tuple_cat (std::tie (glyphs, positions, callbackFont, range, lineMetrics), rest);
const auto invokeNullChecked = [&] (auto&... params)
{ NullCheckedInvocation::invoke (callback, params...); };
std::apply (invokeNullChecked, callbackParameters);
}
}
/* This is how much cumulative widths glyphs take up in each line. Whether the trailing
whitespace is included depends on the ShapedTextOptions::getWhitespaceShouldFitInLine()
setting.
*/
auto& getMinimumRequiredWidthForLines() const { return minimumRequiredWidthsForLine; }
int64 getGlyphIndexToTheRightOf (Point<float> p) const;
/* If the passed in index parameter is greater than the index of the last contained glyph,
then the returned anchor specifies the location where the next glyph would have to be
placed i.e. lastGlyphAnchor + lastGlyphAdvance.
*/
GlyphAnchorResult getGlyphAnchor (int64 glyphIndex) const;
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
/* Returns the vertical distance from the baseline of the first line to the bottom of the last
plus any additional line spacing that follows from the leading and additiveLineSpacing
members of the ShapedTextOptions object.
This guarantees that if ShapedText object1 is drawn at y = 0 and object2 is drawn at
y = object1.getHeight(), then the two texts will be spaced exactly as if they were a single
ShapedText object containing both texts.
*/
float getHeight() const;
const auto& getLineMetricsForGlyphRange() const { return lineMetricsForGlyphRange; }
private:
const SimpleShapedText& shapedText;
detail::RangedValues<LineMetrics> lineMetricsForGlyphRange;
std::optional<SimpleShapedText> ellipsis;
detail::RangedValues<DrawType> rangesToDraw;
detail::RangedValues<float> whitespaceStretch;
std::vector<float> minimumRequiredWidthsForLine;
};
} // namespace juce::detail
@@ -0,0 +1,871 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
#if JUCE_UNIT_TESTS
template <typename T, typename = void>
constexpr auto hasGetStartFunction = false;
template <typename T>
constexpr auto hasGetStartFunction<T, std::void_t<decltype (std::declval<T>().getStart())>> = true;
template <typename RangeType, typename std::enable_if<hasGetStartFunction<RangeType>, int>::type = 0>
std::ostream& operator<< (std::ostream& os, const RangeType& range)
{
os << "[" << range.getStart() << ", " << range.getEnd() << ")";
return os;
}
static String& operator<< (String& s, Range<int64> r)
{
return s += "[" + String { r.getStart() } + ", " + String { r.getEnd() } + ")";
}
template <typename ValueType>
static auto getCumulativeRangeLengths (const RangedValues<ValueType>& rv)
{
int64 totalLength{};
for (size_t i = 0; i < rv.size(); ++i)
totalLength += rv.getItem (i).range.getLength();
return totalLength;
}
template <typename ValueType>
static auto toString (const RangedValues<ValueType>& rv)
{
String s {};
for (size_t i = 0; i < rv.size(); ++i)
{
auto item = rv.getItem (i);
s << item.range << ": " << item.value << "\n";
}
return s;
}
class RangesTestsBase : public UnitTest
{
public:
using UnitTest::UnitTest;
void expectRange (Range<int64> actual, Range<int64> expected)
{
String failureMessage { "range " };
failureMessage << actual << " did not equal expected range " << expected;
expect (actual == expected, failureMessage);
}
};
class RangesTests : public RangesTestsBase
{
public:
RangesTests() : RangesTestsBase ("Ranges", UnitTestCategories::containers) {}
void runTest() override
{
Ranges::Operations ops;
beginTest ("Ranges::set() - basics");
{
Ranges ranges;
ranges.set ({ -3, 14 }, ops);
expectRange (ranges.get (0), { -3, 14 });
ranges.set ({ 7, 20 }, ops);
expectRange (ranges.get (0), { -3, 7 });
expectRange (ranges.get (1), { 7, 20 });
}
beginTest ("Ranges::set() - neighbouring ranges extents are modified");
{
Ranges ranges;
ranges.set ({ -3, 14 }, ops);
ranges.set ({ 19, 30 }, ops);
ranges.set ({ 10, 25 }, ops);
// size_t doesn't always map to an existing overload for String::operator<< on all platforms
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { -3, 10 });
expectRange (ranges.get (1), { 10, 25 });
expectRange (ranges.get (2), { 25, 30 });
}
beginTest ("Ranges::set() - setting a range inside another one splits that range");
{
Ranges ranges;
ranges.set ({ -3, 14 }, ops);
expectEquals ((int64) ranges.size(), (int64) 1);
//==============================================================================
ranges.set ({ 3, 7 }, ops);
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { -3, 3 });
expectRange (ranges.get (1), { 3, 7 });
expectRange (ranges.get (2), { 7, 14 });
}
beginTest ("Ranges::set() - old ranges falling within the bounds of a newly set are erased");
{
Ranges ranges;
ranges.set ({ 0, 5 }, ops);
ranges.set ({ 5, 10 }, ops);
ranges.set ({ 15, 20 }, ops);
ranges.set ({ 25, 30 }, ops);
ranges.set ({ 35, 50 }, ops);
expectEquals ((int64) ranges.size(), (int64) 5);
//==============================================================================
ranges.set ({ 4, 36 }, ops);
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { 0, 4 });
expectRange (ranges.get (1), { 4, 36 });
expectRange (ranges.get (2), { 36, 50 });
}
beginTest ("Ranges::set() - setting an empty range should be a no-op");
{
Ranges ranges;
ops.clear();
ranges.set ({ 0, 0 }, ops);
expect (ranges.isEmpty());
expect (ops.empty());
}
beginTest ("Ranges::set() - setting a range inside another range");
{
Ranges ranges;
ranges.set ({ 0, 48 }, ops);
ranges.set ({ 48, 127 }, ops);
ranges.set ({ 49, 94 }, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 49, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::split()");
{
Ranges ranges;
ranges.set ({ 0, 48 }, ops);
ranges.set ({ 48, 127 }, ops);
ops.clear();
ranges.split (47, ops);
expectEquals ((int64) ops.size(), (int64) 1, "");
const auto op0 = std::get_if<Ranges::Ops::Split> (&ops[0]);
expect (op0 != nullptr);
if (op0 != nullptr)
expectEquals ((int) op0->index, 0, "The 0th element should be split");
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 47 });
expectRange (ranges.get (1), { 47, 48 });
expectRange (ranges.get (2), { 48, 127 });
}
beginTest ("Ranges::split() - splitting has no effect when no range begins before and ends after the location");
{
Ranges ranges;
ranges.set ({ 0, 48 }, ops);
ranges.set ({ 48, 127 }, ops);
ops.clear();
ranges.split (48, ops);
expectEquals ((int64) ops.size(), (int64) 0, "");
expectEquals ((int64) ranges.size(), (int64) 2, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 127 });
}
beginTest ("Ranges::insert() - basics");
{
Ranges ranges;
ranges.insert ({ -3, 14 }, ops);
expectRange (ranges.get (0), { -3, 14 });
ranges.insert ({ 7, 20 }, ops);
expectRange (ranges.get (0), { -3, 7 });
expectRange (ranges.get (1), { 7, 20 });
expectRange (ranges.get (2), { 20, 27 });
}
beginTest ("Ranges::insert() - inserting shifts all following ranges");
{
Ranges ranges;
ranges.insert ({ 10, 11 }, ops);
ranges.insert ({ 0, 1 }, ops);
expectRange (ranges.get (0), { 0, 1 });
expectRange (ranges.get (1), { 11, 12 });
}
beginTest ("Ranges::insert() - inserting an empty range should be a no-op");
{
Ranges ranges;
ops.clear();
ranges.insert ({ 0, 0 }, ops);
expect (ranges.isEmpty());
expect (ops.empty());
}
const auto getTestRanges = [&ops]
{
Ranges ranges;
ranges.set ({ 0, 48 }, ops);
ranges.set ({ 48, 49 }, ops);
ranges.set ({ 55, 94 }, ops);
ranges.set ({ 94, 127 }, ops);
return ranges;
};
beginTest ("Ranges::eraseFrom() - erasing beyond all ranges has no effect");
{
auto ranges = getTestRanges();
ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::eraseFrom() - erasing modifies the range that encloses the starting index");
{
auto ranges = getTestRanges();
ranges.eraseFrom (122, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 122 });
}
beginTest ("Ranges::eraseFrom() - ranges starting after the erased index are deleted entirely");
{
auto ranges = getTestRanges();
ranges.eraseFrom (60, ops);
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 60 });
}
beginTest ("Ranges::eraseFrom() - erasing from a location outside any ranges will still drop subsequent ranges");
{
auto ranges = getTestRanges();
ranges.eraseFrom (51, ops);
expectEquals ((int64) ranges.size(), (int64) 2, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
}
beginTest ("Ranges::erase() - erasing a zero length range has no effect");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 30 }, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing inside a range splits that range");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 31 }, ops);
expectEquals ((int64) ranges.size(), (int64) 5, "");
expectRange (ranges.get (0), { 0, 30 });
expectRange (ranges.get (1), { 31, 48 });
expectRange (ranges.get (2), { 48, 49 });
expectRange (ranges.get (3), { 55, 94 });
expectRange (ranges.get (4), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing a range that completely overlaps existing ranges erases those");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 70 }, ops);
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 30 });
expectRange (ranges.get (1), { 70, 94 });
expectRange (ranges.get (2), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing a range that no previous ranges covered has no effect");
{
auto ranges = getTestRanges();
ranges.erase ({ 51, 53 }, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing over a range beyond all limits clears all ranges");
{
auto ranges = getTestRanges();
ranges.erase ({ -1000, 1000 }, ops);
expect (ranges.isEmpty());
}
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward");
{
auto ranges = getTestRanges();
ranges.drop ({ 48, 49 }, ops);
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 54, 93 });
expectRange (ranges.get (2), { 93, 126 });
}
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward even if no range covered the dropped range previously");
{
auto ranges = getTestRanges();
ranges.drop ({ 51, 53 }, ops);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 53, 92 });
expectRange (ranges.get (3), { 92, 125 });
}
beginTest ("Ranges::drop() - dropping a range covering all other ranges empties the collection");
{
auto ranges = getTestRanges();
ranges.drop ({ -1000, 1000 }, ops);
expect (ranges.isEmpty());
}
beginTest ("Ranges::covers()");
{
Ranges ranges;
ranges.set ({ 0, 48 }, ops);
ranges.set ({ 48, 49 }, ops);
ranges.set ({ 55, 94 }, ops);
ranges.set ({ 94, 127 }, ops);
ranges.set ({ 127, 150 }, ops);
expect (ranges.covers ({ 0, 48 }));
expect (ranges.covers ({ 0, 20 }));
expect (ranges.covers ({ 10, 30 }));
expect (ranges.covers ({ 30, 48 }));
expect (ranges.covers ({ 30, 49 }));
expect (ranges.covers ({ 55, 150 }));
expect (ranges.covers ({ 60, 145 }));
expect (! ranges.covers ({ -1, 10 }));
expect (! ranges.covers ({ 1, 50 }));
expect (! ranges.covers ({ 50, 140 }));
expect (! ranges.covers ({ 149, 151 }));
expect (ranges.covers ({ 10, 10 }));
expect (! ranges.covers ({ 151, 151 }));
}
}
};
class RangedValuesTests : public UnitTest
{
public:
RangedValuesTests() : UnitTest ("RangedValues", UnitTestCategories::containers) {}
template <typename ItemType>
void expectRangedValuesItem (ItemType item, Range<int64> range, char value)
{
{
String failureMessage { "range " };
failureMessage << item.range << " did not equal expected range " << range;
expect (item.range == range, failureMessage);
}
{
String failureMessage { "value '" };
failureMessage << item.value << "' in range " << range << " did not equal expected value '" << value << "'";
expect (item.value == value, failureMessage);
}
}
void runTest() override
{
auto random = getRandom();
Ranges::Operations ops;
const auto createRangedValuesObject = [&]
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a', ops);
rangedValues.set ({ 11, 20 }, 'b', ops);
rangedValues.set ({ 23, 30 }, 'c', ops);
return rangedValues;
};
beginTest ("RangedValues::set() with distinct value overlapping other ranges");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ 5, 15 }, 'd', ops);
expect (! rangedValues.isEmpty());
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'd');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
rangedValues.set ({ 19, 24 }, 'e', ops);
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 19, 24 }, 'e');
expectRangedValuesItem (rangedValues.getItem (4), { 24, 30 }, 'c');
}
beginTest ("RangedValues::set() with distinct value in corner cases");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ -1, 0 }, 'd', ops);
expectRangedValuesItem (rangedValues.getItem (0), { -1, 0 }, 'd');
expectRangedValuesItem (rangedValues.getItem (1), { 0, 10 }, 'a');
}
beginTest ("RangedValues::set() with same value with merging disallowed");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsNo{});
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
}
beginTest ("RangedValues::set() with same value with merging allowed");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsYes{});
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c');
}
beginTest ("RangedValues::set() - setting an empty Range should be a no-op");
{
RangedValues<char> rangedValues;
ops.clear();
rangedValues.set ({ 0, 0 }, 'a', ops);
expect (rangedValues.isEmpty());
expect (ops.empty());
}
beginTest ("RangedValues::set() - setting a range inside another range");
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 48 }, 'a', ops);
rangedValues.set ({ 48, 127 }, 'b', ops);
rangedValues.set ({ 49, 94 }, 'c', ops);
expectEquals ((int64) rangedValues.size(), (int64) 4, "");
expectRangedValuesItem (rangedValues.getItem (0), { 0, 48 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 48, 49 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 49, 94 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 94, 127 }, 'b');
}
beginTest ("RangedValues::getIntersectionsWith()");
{
auto rangedValues = createRangedValuesObject();
{
const auto intersections = rangedValues.getIntersectionsWith ({ 5, 43 });
expectRangedValuesItem (intersections.getItem (0), { 5, 10 }, 'a');
expectRangedValuesItem (intersections.getItem (1), { 11, 20 }, 'b');
expectRangedValuesItem (intersections.getItem (2), { 23, 30 }, 'c');
}
{
const auto intersections = rangedValues.getIntersectionsWith ({ -10, 3 });
expectRangedValuesItem (intersections.getItem (0), { 0, 3 }, 'a');
}
}
beginTest ("RangedValues::insert() fuzzing - insert always increases the total covered range");
{
for (auto i = 0; i != 100; ++i)
{
auto rangedValuesNotMerged = createRangedValuesObject();
auto rangedValuesMerged = createRangedValuesObject();
const auto totalLengthBeforeInsert = getCumulativeRangeLengths (rangedValuesNotMerged);
const auto beginInsertionAt = (int64) random.nextInt (100) - 50;
const auto numElemsToInsert = (int64) random.nextInt (1000);
rangedValuesNotMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
'a' + (char) random.nextInt (25),
ops,
MergeEqualItemsNo{});
expectEquals (getCumulativeRangeLengths (rangedValuesNotMerged) - totalLengthBeforeInsert, numElemsToInsert);
rangedValuesMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
'a' + (char) random.nextInt (25),
ops,
MergeEqualItemsYes{});
expectEquals (getCumulativeRangeLengths (rangedValuesMerged) - totalLengthBeforeInsert, numElemsToInsert);
}
}
beginTest ("RangedValues::insert() with distinct value inside another range");
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert ({ 2, 4 }, 'd', ops);
expectEquals ((int64) rangedValues.size(), (int64) 5);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'd');
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
}
beginTest ("RangedValues::insert() with same value inside another range");
{
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsYes{});
expectEquals ((int64) rangedValues.size(), (int64) 3);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 25, 32 }, 'c');
}
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsNo{});
expectEquals ((int64) rangedValues.size(), (int64) 5);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'a');
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
}
}
beginTest ("RangedValues::insert() - inserting an empty Range should be a no-op");
{
{
RangedValues<char> emptyRangedValues;
ops.clear();
emptyRangedValues.insert ({ 0, 0 }, 'a', ops);
expect (emptyRangedValues.isEmpty());
expect (ops.empty());
}
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a', ops);
ops.clear();
rangedValues.insert ({ 0, 0 }, 'a', ops);
expect (ops.empty());
expectEquals ((int64) rangedValues.size(), (int64) 1);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
}
}
const auto createRangedValuesObjectForErase = [&]
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a', ops);
rangedValues.set ({ 11, 20 }, 'b', ops);
rangedValues.set ({ 23, 30 }, 'c', ops);
rangedValues.set ({ 35, 45 }, 'c', ops);
rangedValues.set ({ 45, 60 }, 'd', ops);
return rangedValues;
};
beginTest ("RangedValues::erase() - erase will not shift subsequent ranges downward");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.erase ({ 15, 16 }, ops);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 16, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 35, 45 }, 'c');
expectRangedValuesItem (rangedValues.getItem (5), { 45, 60 }, 'd');
}
beginTest ("RangedValues::eraseUpTo() - erasing before all ranges has no effect");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.eraseUpTo (rangedValues.getRanges().get (0).getStart(), ops);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 35, 45 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 45, 60 }, 'd');
}
beginTest ("RangedValues::eraseUpTo() - erasing values up to, not including 15");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.eraseUpTo (15, ops);
expectRangedValuesItem (rangedValues.getItem (0), { 15, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (1), { 23, 30 }, 'c');
expectRangedValuesItem (rangedValues.getItem (2), { 35, 45 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 45, 60 }, 'd');
}
beginTest ("RangedValues::eraseUpTo() - erasing up to the end of all ranges clears the container");
{
auto rangedValues = createRangedValuesObjectForErase();
const auto ranges = rangedValues.getRanges();
rangedValues.eraseUpTo (ranges.get (ranges.size() - 1).getEnd(), ops);
expect (rangedValues.isEmpty());
}
beginTest ("RangedValues::drop() - drop shifts ranges downward on the right side");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsNo{});
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 22, 29 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (5), { 44, 59 }, 'd');
}
beginTest ("RangedValues::drop() - drop can be used to merge equal values");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 22, 29 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
}
beginTest ("RangedValues::drop() - the merge happens at the seam caused by the drop and does not extend beyond");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.set ({ 20, 30 }, 'b', ops, MergeEqualItemsNo{});
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
// These two items are not merged, even though they form a contiguous range, because
// they were disjoint before the drop and they don't touch each other at the drop
// seam of 15.
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 19, 29 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
}
}
};
class IntersectingRangedValuesTests : public RangesTestsBase
{
public:
IntersectingRangedValuesTests() : RangesTestsBase ("IntersectingRangedValues", UnitTestCategories::containers) {}
void runTest() override
{
Ranges::Operations ops;
beginTest ("IntersectingRangedValuesTests - iterating over multiple RangedValues");
{
RangedValues<int> rv1;
rv1.set ({ 3, 8}, 1, ops);
rv1.set ({ 9, 16}, 2, ops);
rv1.set ({ 30, 40}, 3, ops);
RangedValues<int> rv2;
rv2.set ({ 0, 4}, 7, ops);
rv2.set ({ 4, 6}, 11, ops);
rv2.set ({ 6, 25}, 13, ops);
rv2.set ({ 27, 55}, 17, ops);
RangedValues<int> rv3;
rv3.set ({ -2, 10}, -1, ops);
rv3.set ({ 15, 19}, -2, ops);
rv3.set ({ 22, 36}, -3, ops);
int iteration = 0;
for (const auto [range, v1, v2, v3] : makeIntersectingRangedValues (&rv1, &rv2, &rv3))
{
if (iteration == 0)
{
expectRange (range, Range<int64> { 3, 4 });
expect (v1 == 1 && v2 == 7 && v3 == -1);
}
if (iteration == 1)
{
expectRange (range, Range<int64> { 4, 6 });
expect (v1 == 1 && v2 == 11 && v3 == -1);
}
if (iteration == 2)
{
expectRange (range, Range<int64> { 6, 8 });
expect (v1 == 1 && v2 == 13 && v3 == -1);
}
if (iteration == 3)
{
expectRange (range, Range<int64> { 9, 10 });
expect (v1 == 2 && v2 == 13 && v3 == -1);
}
if (iteration == 4)
{
expectRange (range, Range<int64> { 15, 16 });
expect (v1 == 2 && v2 == 13 && v3 == -2);
}
if (iteration == 5)
{
expectRange (range, Range<int64> { 30, 36 });
expect (v1 == 3 && v2 == 17 && v3 == -3);
}
++iteration;
}
expectEquals (iteration, 6);
}
}
};
static RangesTests rangesTests;
static RangedValuesTests rangedValuesTests;
static IntersectingRangedValuesTests intersectingRangedValuesTests;
#endif
} // namespace juce::detail
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,232 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
class ShapedText::Impl
{
public:
Impl (String textIn, Options optionsIn)
: options { std::move (optionsIn) },
text { std::move (textIn) }
{
}
void draw (const Graphics& g, AffineTransform transform) const
{
drawJustifiedText (justifiedText, g, transform);
}
float getHeight() const
{
return justifiedText.getHeight();
}
int64 getNumGlyphs() const
{
return simpleShapedText.getNumGlyphs();
}
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const
{
return justifiedText.getLineMetricsForGlyphRange();
}
const detail::Ranges& getLineTextRanges() const
{
return simpleShapedText.getLineTextRanges();
}
auto& getText() const
{
return text;
}
auto getTextRange (int64 glyphIndex) const
{
return simpleShapedText.getTextRange (glyphIndex);
}
auto isLtr (int64 glyphIndex) const
{
return simpleShapedText.isLtr (glyphIndex);
}
int64 getTextIndexForCaret (Point<float> p) const
{
const auto getGlyph = [&] (int64 i)
{
return simpleShapedText.getGlyphs()[(size_t) i];
};
if (getNumGlyphs() == 0)
return 0;
const auto glyphOnTheRight = justifiedText.getGlyphIndexToTheRightOf (p);
if (glyphOnTheRight >= getNumGlyphs())
{
const auto glyphOnTheLeft = glyphOnTheRight - 1;
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheLeft).cluster)->value.ltr;
if (ltr)
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheLeft);
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheLeft].cluster;
}
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheRight).cluster)->value.ltr;
if (ltr)
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheRight].cluster;
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheRight);
}
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
{
simpleShapedText.getGlyphRanges (textRange, outRanges);
}
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const
{
return justifiedText.getGlyphsBounds (glyphRange);
}
auto getGlyphAnchor (int64 index) const
{
return justifiedText.getGlyphAnchor (index);
}
Span<const float> getMinimumRequiredWidthForLines() const
{
return justifiedText.getMinimumRequiredWidthForLines();
}
//==============================================================================
auto& getSimpleShapedText() const { return simpleShapedText; }
auto& getJustifiedText() const { return justifiedText; }
private:
ShapedTextOptions options;
String text;
SimpleShapedText simpleShapedText { &text, options };
JustifiedText justifiedText { &simpleShapedText, options };
};
ShapedText::ShapedText() : ShapedText ("", {})
{
}
ShapedText::ShapedText (String text) : ShapedText (std::move (text), {})
{
}
ShapedText::ShapedText (String text, Options options)
{
impl = std::make_shared<Impl> (std::move (text), std::move (options));
}
void ShapedText::draw (const Graphics& g, AffineTransform transform) const
{
impl->draw (g, transform);
}
float ShapedText::getHeight() const
{
return impl->getHeight();
}
int64 ShapedText::getNumGlyphs() const
{
return impl->getNumGlyphs();
}
const detail::RangedValues<LineMetrics>& ShapedText::getLineMetricsForGlyphRange() const
{
return impl->getLineMetricsForGlyphRange();
}
const detail::Ranges& ShapedText::getLineTextRanges() const
{
return impl->getLineTextRanges();
}
const String& ShapedText::getText() const
{
return impl->getText();
}
Range<int64> ShapedText::getTextRange (int64 glyphIndex) const
{
return impl->getTextRange (glyphIndex);
}
bool ShapedText::isLtr (int64 glyphIndex) const
{
return impl->isLtr (glyphIndex);
}
int64 ShapedText::getTextIndexForCaret (Point<float> p) const
{
return impl->getTextIndexForCaret (p);
}
void ShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
{
return impl->getGlyphRanges (textRange, outRanges);
}
RectangleList<float> ShapedText::getGlyphsBounds (Range<int64> glyphRange) const
{
return impl->getGlyphsBounds (glyphRange);
}
GlyphAnchorResult ShapedText::getGlyphAnchor (int64 index) const
{
return impl->getGlyphAnchor (index);
}
Span<const float> ShapedText::getMinimumRequiredWidthForLines() const
{
return impl->getMinimumRequiredWidthForLines();
}
const JustifiedText& ShapedText::getJustifiedText() const { return impl->getJustifiedText(); }
const SimpleShapedText& ShapedText::getSimpleShapedText() const { return impl->getSimpleShapedText(); }
} // namespace juce::detail
@@ -0,0 +1,116 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
/* Class that can visually shape a Unicode string provided a list of Fonts corresponding to
sub-ranges of the string.
*/
class JUCE_API ShapedText
{
public:
using Options = ShapedTextOptions;
ShapedText();
explicit ShapedText (String text);
ShapedText (String text, Options options);
/* Returns the text which was used to construct this object. */
const String& getText() const;
Span<const ShapedGlyph> getGlyphs() const;
/* Returns the text's codepoint range, to which the glyph under the provided index belongs.
This range will have a length of at least one, and potentially more than one if ligatures
are enabled.
*/
Range<int64> getTextRange (int64 glyphIndex) const;
bool isLtr (int64 glyphIndex) const;
int64 getTextIndexForCaret (Point<float> p) const;
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
/* @see JustifiedText::getGlyphAnchor() */
GlyphAnchorResult getGlyphAnchor (int64 index) const;
/* Returns the widths for each line, that the glyphs would require to be rendered without being
truncated. This will or will not include the space required by trailing whitespaces in the
line based on the ShapedTextOptions::withTrailingWhitespacesShouldFit() value.
This value isn't affected by the Justification parameter, it just reports the amount of
width that would be required to avoid truncation.
*/
Span<const float> getMinimumRequiredWidthForLines() const;
/* @see JustifiedText::accessTogetherWith */
template <typename Callable, typename... RangedValues>
void accessTogetherWith (Callable&& callback, RangedValues&&... rangedValues) const
{
getJustifiedText().accessTogetherWith (std::forward<Callable> (callback),
std::forward<RangedValues> (rangedValues)...);
}
/* Draws the text. */
void draw (const Graphics& g, AffineTransform transform) const;
/* @see JustifiedText::getHeight
*/
float getHeight() const;
int64 getNumGlyphs() const;
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const;
const detail::Ranges& getLineTextRanges() const;
/* @internal */
const JustifiedText& getJustifiedText() const;
/* @internal */
const SimpleShapedText& getSimpleShapedText() const;
private:
class Impl;
std::shared_ptr<Impl> impl;
};
} // namespace juce::detail
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,346 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
/** Types of text direction. This may also be applied to characters. */
enum class TextDirection
{
ltr, // This text reads left to right.
rtl // This text reads right to left.
};
class ShapedTextOptions
{
private:
auto tie() const
{
return std::tie (justification,
readingDir,
maxWidth,
alignmentWidth,
height,
fontsForRange,
language,
firstLineIndent,
leading,
additiveLineSpacing,
baselineAtZero,
allowBreakingInsideWord,
trailingWhitespacesShouldFit,
maxNumLines,
ellipsis);
}
public:
bool operator== (const ShapedTextOptions& other) const { return tie() == other.tie(); }
bool operator!= (const ShapedTextOptions& other) const { return tie() != other.tie(); }
//==============================================================================
[[nodiscard]] ShapedTextOptions withJustification (Justification x) const
{
return withMember (*this, &ShapedTextOptions::justification, x);
}
/* This option will use soft wrapping for lines that are longer than the specified value,
and it will also align each line to this width, using the Justification provided in
withJustification.
The alignment width can be overriden using withAlignmentWidth, but currently we only need
to do this for the TextEditor.
*/
[[nodiscard]] ShapedTextOptions withMaxWidth (float x) const
{
return withMember (*this, &ShapedTextOptions::maxWidth, x);
}
/* With this option each line will be aligned only if it's shorter or equal to the alignment
width. Otherwise, the line's x anchor will be 0.0f. This is in contrast to using
withMaxWidth only, which will modify the x anchor of RTL lines that are too long, to ensure
that it's the logical end of the text that falls outside the visible bounds.
The alignment width is also a distinct value from the value used for soft wrapping which is
specified using withMaxWidth.
This option is specifically meant to support an existing TextEditor behaviour, where text
can be aligned even when word wrapping is off. You probably don't need to use this function,
unless you want to reproduce the particular behaviour seen in the TextEditor, and should
only use withMaxWidth, if alignment is required.
With this option off, text is either not aligned, or aligned to the width specified using
withMaxWidth.
When this option is in use, it overrides the width specified in withMaxWidth for alignment
purposes, but not for line wrapping purposes.
It also accommodates the fact that the TextEditor has a scrolling feature and text never
becomes unreachable, even if the lines are longer than the viewport's width.
*/
[[nodiscard]] ShapedTextOptions withAlignmentWidth (float x) const
{
return withMember (*this, &ShapedTextOptions::alignmentWidth, x);
}
[[nodiscard]] ShapedTextOptions withHeight (float x) const
{
return withMember (*this, &ShapedTextOptions::height, x);
}
[[nodiscard]] ShapedTextOptions withFont (Font x) const
{
RangedValues<Font> fonts;
detail::Ranges::Operations ops;
fonts.set ({ 0, std::numeric_limits<int64>::max() }, x, ops);
return withMember (*this, &ShapedTextOptions::fontsForRange, std::move (fonts));
}
[[nodiscard]] ShapedTextOptions withFonts (const detail::RangedValues<Font>& x) const
{
return withMember (*this, &ShapedTextOptions::fontsForRange, x);
}
[[nodiscard]] ShapedTextOptions withLanguage (StringRef x) const
{
return withMember (*this, &ShapedTextOptions::language, x);
}
[[nodiscard]] ShapedTextOptions withFirstLineIndent (float x) const
{
return withMember (*this, &ShapedTextOptions::firstLineIndent, x);
}
/* This controls the space between lines using a proportional value, with a default of 1.0,
meaning single line spacing i.e. the descender of the current line + ascender of the next
line. This value is multiplied by the leading provided here.
*/
[[nodiscard]] ShapedTextOptions withLeading (float x) const
{
return withMember (*this, &ShapedTextOptions::leading, x);
}
/* This controls the space between lines using an additive absolute value, with a default of 0.0.
This value is added to the spacing between each two lines.
*/
[[nodiscard]] ShapedTextOptions withAdditiveLineSpacing (float x) const
{
return withMember (*this, &ShapedTextOptions::additiveLineSpacing, x);
}
[[nodiscard]] ShapedTextOptions withBaselineAtZero (bool x = true) const
{
return withMember (*this, &ShapedTextOptions::baselineAtZero, x);
}
[[nodiscard]] ShapedTextOptions withTrailingWhitespacesShouldFit (bool x = true) const
{
return withMember (*this, &ShapedTextOptions::trailingWhitespacesShouldFit, x);
}
[[nodiscard]] ShapedTextOptions withMaxNumLines (int64 x) const
{
return withMember (*this, &ShapedTextOptions::maxNumLines, x);
}
[[nodiscard]] ShapedTextOptions withEllipsis (String x = String::charToString ((juce_wchar) 0x2026)) const
{
return withMember (*this, &ShapedTextOptions::ellipsis, std::move (x));
}
[[nodiscard]] ShapedTextOptions withReadingDirection (std::optional<TextDirection> x) const
{
return withMember (*this, &ShapedTextOptions::readingDir, x);
}
[[nodiscard]] ShapedTextOptions withAllowBreakingInsideWord (bool x = true) const
{
return withMember (*this, &ShapedTextOptions::allowBreakingInsideWord, x);
}
const auto& getReadingDirection() const { return readingDir; }
const auto& getJustification() const { return justification; }
const auto& getMaxWidth() const { return maxWidth; }
const auto& getAlignmentWidth() const { return alignmentWidth; }
const auto& getHeight() const { return height; }
const auto& getFontsForRange() const { return fontsForRange; }
const auto& getLanguage() const { return language; }
const auto& getFirstLineIndent() const { return firstLineIndent; }
const auto& getLeading() const { return leading; }
const auto& getAdditiveLineSpacing() const { return additiveLineSpacing; }
const auto& isBaselineAtZero() const { return baselineAtZero; }
const auto& getTrailingWhitespacesShouldFit() const { return trailingWhitespacesShouldFit; }
const auto& getMaxNumLines() const { return maxNumLines; }
const auto& getEllipsis() const { return ellipsis; }
const auto& getAllowBreakingInsideWord() const { return allowBreakingInsideWord; }
private:
Justification justification { Justification::topLeft };
std::optional<TextDirection> readingDir;
std::optional<float> maxWidth;
std::optional<float> alignmentWidth;
std::optional<float> height;
detail::RangedValues<Font> fontsForRange = std::invoke ([&]
{
detail::RangedValues<Font> result;
detail::Ranges::Operations ops;
result.set ({ 0, std::numeric_limits<int64>::max() }, FontOptions { 15.0f }, ops);
return result;
});
String language = SystemStats::getDisplayLanguage();
float firstLineIndent = 0.0f;
float leading = 1.0f;
float additiveLineSpacing = 0.0f;
bool baselineAtZero = false;
bool allowBreakingInsideWord = false;
bool trailingWhitespacesShouldFit = true;
int64 maxNumLines = std::numeric_limits<int64>::max();
String ellipsis;
};
struct ShapedGlyph
{
ShapedGlyph (uint32_t glyphIdIn,
int64 clusterIn,
bool unsafeToBreakIn,
bool whitespaceIn,
bool newlineIn,
int8_t distanceFromLigatureIn,
Point<float> advanceIn,
Point<float> offsetIn)
: advance (advanceIn),
offset (offsetIn),
cluster (clusterIn),
glyphId (glyphIdIn),
unsafeToBreak (unsafeToBreakIn),
whitespace (whitespaceIn),
newline (newlineIn),
distanceFromLigature (distanceFromLigatureIn) {}
bool isUnsafeToBreak() const { return unsafeToBreak; }
bool isWhitespace() const { return whitespace; }
bool isNewline() const { return newline; }
bool isNonLigature() const { return distanceFromLigature == 0; }
bool isLigature() const { return distanceFromLigature < 0; }
bool isPlaceholderForLigature() const { return distanceFromLigature > 0; }
int8_t getDistanceFromLigature() const { return distanceFromLigature; }
int8_t getNumTrailingLigaturePlaceholders() const { return -distanceFromLigature; }
Point<float> advance;
Point<float> offset;
int64 cluster;
uint32_t glyphId;
private:
// These are effectively bools, pack into a single int once we have more than four flags.
int8_t unsafeToBreak;
int8_t whitespace;
int8_t newline;
int8_t distanceFromLigature;
};
struct GlyphLookupEntry
{
Range<int64> glyphRange;
bool ltr = true;
};
class SimpleShapedText
{
public:
/* Shapes and lays out the first contiguous sequence of ranges specified in the fonts
parameter.
*/
SimpleShapedText (const String* data,
const ShapedTextOptions& options);
const auto& getLineNumbersForGlyphRanges() const { return lineNumbersForGlyphRanges; }
const auto& getLineTextRanges() const { return lineTextRanges; }
const auto& getResolvedFonts() const { return resolvedFonts; }
Range<int64> getTextRange (int64 glyphIndex) const;
/* Returns true if the specified glyph is inside to an LTR run.
*/
bool isLtr (int64 glyphIndex) const;
/* This function may fail to return an out range, even if the provided textRange falls inside
the string range used for the creation of the ShapedText object.
This is because the shaping process could fail due to insufficient glyph resolution to the
point, where it will produce zero glyphs for the provided string.
*/
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
/* Returns the input codepoint index that follows the glyph in a logical sense. So for LTR text
this is the cluster number of the glyph to the right. For RTL text it's the one on the left.
If there is no subsequent glyph, the returned number is the first Unicode codepoint index
that isn't covered by the cluster to which the selected glyph belongs, so for the glyph 'o'
in "hello" this would be 5, given there are no ligatures in use.
*/
int64 getTextIndexAfterGlyph (int64 glyphIndex) const;
int64 getNumLines() const { return (int64) lineNumbersForGlyphRanges.getRanges().size(); }
int64 getNumGlyphs() const { return (int64) glyphsInVisualOrder.size(); }
juce_wchar getCodepoint (int64 glyphIndex) const;
Span<const ShapedGlyph> getGlyphs (Range<int64> glyphRange) const;
Span<const ShapedGlyph> getGlyphs() const;
const auto& getGlyphLookup() const { return glyphLookup; }
private:
void shape (const String& data,
const ShapedTextOptions& options);
const String& string;
std::vector<ShapedGlyph> glyphsInVisualOrder;
detail::RangedValues<int64> lineNumbersForGlyphRanges;
detail::Ranges lineTextRanges;
detail::RangedValues<Font> resolvedFonts;
detail::RangedValues<GlyphLookupEntry> glyphLookup;
JUCE_LEAK_DETECTOR (SimpleShapedText)
};
} // namespace juce::detail
@@ -0,0 +1,45 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
struct UnicodeHelpers
{
UnicodeHelpers() = delete;
static std::vector<int> getLineBreaks (const String& data);
};
} // namespace juce::detail
@@ -0,0 +1,152 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
DropShadow::DropShadow (Colour shadowColour, const int r, Point<int> o) noexcept
: colour (shadowColour), radius (r), offset (o)
{
jassert (radius > 0);
}
void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const
{
jassert (radius > 0);
if (! srcImage.isValid())
return;
auto blurred = srcImage.convertedToFormat (Image::SingleChannel);
blurred.setBackupEnabled (false);
blurred.getPixelData()->applySingleChannelBoxBlurEffect (radius);
g.setColour (colour);
g.drawImageAt (blurred, offset.x, offset.y, true);
}
void DropShadow::drawForPath (Graphics& g, const Path& path) const
{
jassert (radius > 0);
auto area = (path.getBounds().getSmallestIntegerContainer() + offset)
.expanded (radius + 1)
.getIntersection (g.getClipBounds().expanded (radius + 1));
if (area.getWidth() <= 2 || area.getHeight() <= 2)
return;
Image pathImage { Image::SingleChannel, area.getWidth(), area.getHeight(), true };
pathImage.setBackupEnabled (false);
{
Graphics g2 (pathImage);
g2.setColour (Colours::white);
g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()),
(float) (offset.y - area.getY())));
}
pathImage.getPixelData()->applySingleChannelBoxBlurEffect (radius);
g.setColour (colour);
g.drawImageAt (pathImage, area.getX(), area.getY(), true);
}
static void drawShadowSection (Graphics& g, ColourGradient& cg, Rectangle<float> area,
bool isCorner, float centreX, float centreY, float edgeX, float edgeY)
{
cg.point1 = area.getRelativePoint (centreX, centreY);
cg.point2 = area.getRelativePoint (edgeX, edgeY);
cg.isRadial = isCorner;
g.setGradientFill (cg);
g.fillRect (area);
}
void DropShadow::drawForRectangle (Graphics& g, const Rectangle<int>& targetArea) const
{
ColourGradient cg (colour, 0, 0, colour.withAlpha (0.0f), 0, 0, false);
for (float i = 0.05f; i < 1.0f; i += 0.1f)
cg.addColour (1.0 - i, colour.withMultipliedAlpha (i * i));
const float radiusInset = (float) radius / 2.0f;
const float expandedRadius = (float) radius + radiusInset;
auto area = targetArea.toFloat().reduced (radiusInset) + offset.toFloat();
auto r = area.expanded (expandedRadius);
auto top = r.removeFromTop (expandedRadius);
auto bottom = r.removeFromBottom (expandedRadius);
drawShadowSection (g, cg, top.removeFromLeft (expandedRadius), true, 1.0f, 1.0f, 0, 1.0f);
drawShadowSection (g, cg, top.removeFromRight (expandedRadius), true, 0, 1.0f, 1.0f, 1.0f);
drawShadowSection (g, cg, top, false, 0, 1.0f, 0, 0);
drawShadowSection (g, cg, bottom.removeFromLeft (expandedRadius), true, 1.0f, 0, 0, 0);
drawShadowSection (g, cg, bottom.removeFromRight (expandedRadius), true, 0, 0, 1.0f, 0);
drawShadowSection (g, cg, bottom, false, 0, 0, 0, 1.0f);
drawShadowSection (g, cg, r.removeFromLeft (expandedRadius), false, 1.0f, 0, 0, 0);
drawShadowSection (g, cg, r.removeFromRight (expandedRadius), false, 0, 0, 1.0f, 0);
g.setColour (colour);
g.fillRect (area);
}
//==============================================================================
DropShadowEffect::DropShadowEffect() = default;
DropShadowEffect::~DropShadowEffect() = default;
void DropShadowEffect::setShadowProperties (const DropShadow& newShadow)
{
shadow = newShadow;
}
void DropShadowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha)
{
DropShadow s (shadow);
s.radius = roundToInt ((float) s.radius * scaleFactor);
s.colour = s.colour.withMultipliedAlpha (alpha);
s.offset.x = roundToInt ((float) s.offset.x * scaleFactor);
s.offset.y = roundToInt ((float) s.offset.y * scaleFactor);
s.drawForImage (g, image);
g.setOpacity (alpha);
g.drawImageAt (image, 0, 0);
}
} // namespace juce
@@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Defines a drop-shadow effect.
@tags{Graphics}
*/
struct JUCE_API DropShadow
{
/** Creates a default drop-shadow effect. */
DropShadow() = default;
/** Creates a drop-shadow object with the given parameters. */
DropShadow (Colour shadowColour, int radius, Point<int> offset) noexcept;
/** Renders a drop-shadow based on the alpha-channel of the given image. */
void drawForImage (Graphics& g, const Image& srcImage) const;
/** Renders a drop-shadow based on the shape of a path. */
void drawForPath (Graphics& g, const Path& path) const;
/** Renders a drop-shadow for a rectangle.
Note that for speed, this approximates the shadow using gradients.
*/
void drawForRectangle (Graphics& g, const Rectangle<int>& area) const;
/** The colour with which to render the shadow.
In most cases you'll probably want to leave this as black with an alpha
value of around 0.5
*/
Colour colour { 0x90000000 };
/** The approximate spread of the shadow. */
int radius { 4 };
/** The offset of the shadow. */
Point<int> offset;
};
//==============================================================================
/**
An effect filter that adds a drop-shadow behind the image's content.
(This will only work on images/components that aren't opaque, of course).
When added to a component, this effect will draw a soft-edged
shadow based on what gets drawn inside it. The shadow will also
be applied to the component's children.
For speed, this doesn't use a proper gaussian blur, but cheats by
using a simple bilinear filter. If you need a really high-quality
shadow, check out ImageConvolutionKernel::createGaussianBlur()
@see Component::setComponentEffect
@tags{Graphics}
*/
class JUCE_API DropShadowEffect : public ImageEffectFilter
{
public:
//==============================================================================
/** Creates a default drop-shadow effect.
To customise the shadow's appearance, use the setShadowProperties() method.
*/
DropShadowEffect();
/** Destructor. */
~DropShadowEffect() override;
//==============================================================================
/** Sets up parameters affecting the shadow's appearance. */
void setShadowProperties (const DropShadow& newShadow);
//==============================================================================
/** @internal */
void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha) override;
private:
//==============================================================================
DropShadow shadow;
JUCE_LEAK_DETECTOR (DropShadowEffect)
};
} // namespace juce
@@ -0,0 +1,63 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
GlowEffect::GlowEffect() = default;
GlowEffect::~GlowEffect() = default;
void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point<int> pos)
{
radius = newRadius;
colour = newColour;
offset = pos;
}
void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha)
{
auto blurred = image.createCopy();
blurred.setBackupEnabled (false);
if (auto ptr = blurred.getPixelData())
ptr->applyGaussianBlurEffect (radius * scaleFactor);
g.setColour (colour.withMultipliedAlpha (alpha));
g.drawImageAt (blurred, offset.x, offset.y, true);
g.setOpacity (alpha);
g.drawImageAt (image, offset.x, offset.y, false);
}
} // namespace juce
@@ -0,0 +1,85 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A component effect that adds a coloured blur around the component's contents.
(This will only work on non-opaque components).
@see Component::setComponentEffect, DropShadowEffect
@tags{Graphics}
*/
class JUCE_API GlowEffect : public ImageEffectFilter
{
public:
//==============================================================================
/** Creates a default 'glow' effect.
To customise its appearance, use the setGlowProperties() method.
*/
GlowEffect();
/** Destructor. */
~GlowEffect() override;
//==============================================================================
/** Sets the glow's radius and colour.
The radius is how large the blur should be, and the colour is
used to render it (for a less intense glow, lower the colour's
opacity).
*/
void setGlowProperties (float newRadius,
Colour newColour,
Point<int> offset = {});
//==============================================================================
/** @internal */
void applyEffect (Image&, Graphics&, float scaleFactor, float alpha) override;
private:
//==============================================================================
float radius = 2.0f;
Colour colour { Colours::white };
Point<int> offset;
JUCE_LEAK_DETECTOR (GlowEffect)
};
} // namespace juce
@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A graphical effect filter that can be applied to components.
An ImageEffectFilter can be applied to the image that a component
paints before it hits the screen.
This is used for adding effects like shadows, blurs, etc.
@see Component::setComponentEffect
@tags{Graphics}
*/
class JUCE_API ImageEffectFilter
{
public:
//==============================================================================
/** Overridden to render the effect.
The implementation of this method must use the image that is passed in
as its source, and should render its output to the graphics context passed in.
@param sourceImage the image that the source component has just rendered with
its paint() method. The image may or may not have an alpha
channel, depending on whether the component is opaque.
@param destContext the graphics context to use to draw the resultant image.
@param scaleFactor a scale factor that has been applied to the image - e.g. if
this is 2, then the image is actually scaled-up to twice the
original resolution
@param alpha the alpha with which to draw the resultant image to the
target context
*/
virtual void applyEffect (Image& sourceImage,
Graphics& destContext,
float scaleFactor,
float alpha) = 0;
/** Destructor. */
virtual ~ImageEffectFilter() = default;
};
} // namespace juce
@@ -0,0 +1,42 @@
HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
For parts of HarfBuzz that are licensed under different licenses see individual
files names COPYING in subdirectories where applicable.
Copyright © 2010-2022 Google, Inc.
Copyright © 2015-2020 Ebrahim Byagowi
Copyright © 2019,2020 Facebook, Inc.
Copyright © 2012,2015 Mozilla Foundation
Copyright © 2011 Codethink Limited
Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
Copyright © 2009 Keith Stribley
Copyright © 2011 Martin Hosken and SIL International
Copyright © 2007 Chris Wilson
Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod
Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc.
Copyright © 1998-2005 David Turner and Werner Lemberg
Copyright © 2016 Igalia S.L.
Copyright © 2022 Matthias Clasen
Copyright © 2018,2021 Khaled Hosny
Copyright © 2018,2019,2020 Adobe, Inc
Copyright © 2013-2015 Alexei Podtelezhnikov
For full copyright notices consult the individual files in the package.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the
above copyright notice and the following two paragraphs appear in
all copies of this software.
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
/*
* Copyright © 2018 Ebrahim Byagowi
* Copyright © 2020 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
*/
#ifndef OT_COLOR_COLR_COLRV1_CLOSURE_HH
#define OT_COLOR_COLR_COLRV1_CLOSURE_HH
#include "../../../hb-open-type.hh"
#include "COLR.hh"
/*
* COLR -- Color
* https://docs.microsoft.com/en-us/typography/opentype/spec/colr
*/
namespace OT {
HB_INTERNAL void PaintColrLayers::closurev1 (hb_colrv1_closure_context_t* c) const
{
c->add_layer_indices (firstLayerIndex, numLayers);
const LayerList &paint_offset_lists = c->get_colr_table ()->get_layerList ();
for (unsigned i = firstLayerIndex; i < firstLayerIndex + numLayers; i++)
{
const Paint &paint = std::addressof (paint_offset_lists) + paint_offset_lists[i];
paint.dispatch (c);
}
}
HB_INTERNAL void PaintGlyph::closurev1 (hb_colrv1_closure_context_t* c) const
{
c->add_glyph (gid);
(this+paint).dispatch (c);
}
HB_INTERNAL void PaintColrGlyph::closurev1 (hb_colrv1_closure_context_t* c) const
{
const COLR *colr_table = c->get_colr_table ();
const BaseGlyphPaintRecord* baseglyph_paintrecord = colr_table->get_base_glyph_paintrecord (gid);
if (!baseglyph_paintrecord) return;
c->add_glyph (gid);
const BaseGlyphList &baseglyph_list = colr_table->get_baseglyphList ();
(&baseglyph_list+baseglyph_paintrecord->paint).dispatch (c);
}
template <template<typename> class Var>
HB_INTERNAL void PaintTransform<Var>::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
(this+transform).closurev1 (c);
}
HB_INTERNAL void PaintTranslate::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 2;
}
HB_INTERNAL void PaintScale::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 2;
}
HB_INTERNAL void PaintScaleAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 4;
}
HB_INTERNAL void PaintScaleUniform::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 1;
}
HB_INTERNAL void PaintScaleUniformAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 3;
}
HB_INTERNAL void PaintRotate::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 1;
}
HB_INTERNAL void PaintRotateAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 3;
}
HB_INTERNAL void PaintSkew::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 2;
}
HB_INTERNAL void PaintSkewAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
c->num_var_idxes = 4;
}
HB_INTERNAL void PaintComposite::closurev1 (hb_colrv1_closure_context_t* c) const
{
(this+src).dispatch (c);
(this+backdrop).dispatch (c);
}
} /* namespace OT */
#endif /* OT_COLOR_COLR_COLRV1_CLOSURE_HH */
@@ -0,0 +1,358 @@
/*
* Copyright © 2016 Google, Inc.
* Copyright © 2018 Ebrahim Byagowi
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Sascha Brawer
*/
#ifndef OT_COLOR_CPAL_CPAL_HH
#define OT_COLOR_CPAL_CPAL_HH
#include "../../../hb-open-type.hh"
#include "../../../hb-ot-color.h"
#include "../../../hb-ot-name.h"
/*
* CPAL -- Color Palette
* https://docs.microsoft.com/en-us/typography/opentype/spec/cpal
*/
#define HB_OT_TAG_CPAL HB_TAG('C','P','A','L')
namespace OT {
struct CPALV1Tail
{
friend struct CPAL;
private:
hb_ot_color_palette_flags_t get_palette_flags (const void *base,
unsigned int palette_index,
unsigned int palette_count) const
{
if (!paletteFlagsZ) return HB_OT_COLOR_PALETTE_FLAG_DEFAULT;
return (hb_ot_color_palette_flags_t) (uint32_t)
(base+paletteFlagsZ).as_array (palette_count)[palette_index];
}
hb_ot_name_id_t get_palette_name_id (const void *base,
unsigned int palette_index,
unsigned int palette_count) const
{
if (!paletteLabelsZ) return HB_OT_NAME_ID_INVALID;
return (base+paletteLabelsZ).as_array (palette_count)[palette_index];
}
hb_ot_name_id_t get_color_name_id (const void *base,
unsigned int color_index,
unsigned int color_count) const
{
if (!colorLabelsZ) return HB_OT_NAME_ID_INVALID;
return (base+colorLabelsZ).as_array (color_count)[color_index];
}
public:
void collect_name_ids (const void *base,
unsigned palette_count,
unsigned color_count,
const hb_map_t *color_index_map,
hb_set_t *nameids_to_retain /* OUT */) const
{
if (paletteLabelsZ)
{
+ (base+paletteLabelsZ).as_array (palette_count)
| hb_sink (nameids_to_retain)
;
}
if (colorLabelsZ)
{
const hb_array_t<const NameID> colorLabels = (base+colorLabelsZ).as_array (color_count);
for (unsigned i = 0; i < color_count; i++)
{
if (!color_index_map->has (i)) continue;
nameids_to_retain->add (colorLabels[i]);
}
}
}
bool serialize (hb_serialize_context_t *c,
unsigned palette_count,
unsigned color_count,
const void *base,
const hb_map_t *color_index_map) const
{
TRACE_SERIALIZE (this);
auto *out = c->allocate_size<CPALV1Tail> (static_size);
if (unlikely (!out)) return_trace (false);
out->paletteFlagsZ = 0;
if (paletteFlagsZ)
out->paletteFlagsZ.serialize_copy (c, paletteFlagsZ, base, 0, hb_serialize_context_t::Head, palette_count);
out->paletteLabelsZ = 0;
if (paletteLabelsZ)
out->paletteLabelsZ.serialize_copy (c, paletteLabelsZ, base, 0, hb_serialize_context_t::Head, palette_count);
const hb_array_t<const NameID> colorLabels = (base+colorLabelsZ).as_array (color_count);
if (colorLabelsZ)
{
c->push ();
for (unsigned i = 0; i < color_count; i++)
{
if (!color_index_map->has (i)) continue;
if (!c->copy<NameID> (colorLabels[i]))
{
c->pop_discard ();
return_trace (false);
}
}
c->add_link (out->colorLabelsZ, c->pop_pack ());
}
return_trace (true);
}
bool sanitize (hb_sanitize_context_t *c,
const void *base,
unsigned int palette_count,
unsigned int color_count) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
(!paletteFlagsZ || (base+paletteFlagsZ).sanitize (c, palette_count)) &&
(!paletteLabelsZ || (base+paletteLabelsZ).sanitize (c, palette_count)) &&
(!colorLabelsZ || (base+colorLabelsZ).sanitize (c, color_count)));
}
protected:
// TODO(garretrieger): these offsets can hold nulls so we should not be using non-null offsets
// here. Currently they are needed since UnsizedArrayOf doesn't define null_size
NNOffset32To<UnsizedArrayOf<HBUINT32>>
paletteFlagsZ; /* Offset from the beginning of CPAL table to
* the Palette Type Array. Set to 0 if no array
* is provided. */
NNOffset32To<UnsizedArrayOf<NameID>>
paletteLabelsZ; /* Offset from the beginning of CPAL table to
* the palette labels array. Set to 0 if no
* array is provided. */
NNOffset32To<UnsizedArrayOf<NameID>>
colorLabelsZ; /* Offset from the beginning of CPAL table to
* the color labels array. Set to 0
* if no array is provided. */
public:
DEFINE_SIZE_STATIC (12);
};
typedef HBUINT32 BGRAColor;
struct CPAL
{
static constexpr hb_tag_t tableTag = HB_OT_TAG_CPAL;
bool has_data () const { return numPalettes; }
unsigned int get_size () const
{ return min_size + numPalettes * sizeof (colorRecordIndicesZ[0]); }
unsigned int get_palette_count () const { return numPalettes; }
unsigned int get_color_count () const { return numColors; }
hb_ot_color_palette_flags_t get_palette_flags (unsigned int palette_index) const
{ return v1 ().get_palette_flags (this, palette_index, numPalettes); }
hb_ot_name_id_t get_palette_name_id (unsigned int palette_index) const
{ return v1 ().get_palette_name_id (this, palette_index, numPalettes); }
hb_ot_name_id_t get_color_name_id (unsigned int color_index) const
{ return v1 ().get_color_name_id (this, color_index, numColors); }
unsigned int get_palette_colors (unsigned int palette_index,
unsigned int start_offset,
unsigned int *color_count, /* IN/OUT. May be NULL. */
hb_color_t *colors /* OUT. May be NULL. */) const
{
if (unlikely (palette_index >= numPalettes))
{
if (color_count) *color_count = 0;
return 0;
}
unsigned int start_index = colorRecordIndicesZ[palette_index];
hb_array_t<const BGRAColor> all_colors ((this+colorRecordsZ).arrayZ, numColorRecords);
hb_array_t<const BGRAColor> palette_colors = all_colors.sub_array (start_index,
numColors);
if (color_count)
{
+ palette_colors.sub_array (start_offset, color_count)
| hb_sink (hb_array (colors, *color_count))
;
}
return numColors;
}
void collect_name_ids (const hb_map_t *color_index_map,
hb_set_t *nameids_to_retain /* OUT */) const
{
if (version == 1)
{
hb_barrier ();
v1 ().collect_name_ids (this, numPalettes, numColors, color_index_map, nameids_to_retain);
}
}
private:
const CPALV1Tail& v1 () const
{
if (version == 0) return Null (CPALV1Tail);
hb_barrier ();
return StructAfter<CPALV1Tail> (*this);
}
public:
bool serialize (hb_serialize_context_t *c,
const hb_array_t<const HBUINT16> &color_record_indices,
const hb_array_t<const BGRAColor> &color_records,
const hb_vector_t<unsigned>& first_color_index_for_layer,
const hb_map_t& first_color_to_layer_index,
const hb_set_t &retained_color_indices) const
{
TRACE_SERIALIZE (this);
// TODO(grieger): limit total final size.
for (const auto idx : color_record_indices)
{
hb_codepoint_t layer_index = first_color_to_layer_index[idx];
HBUINT16 new_idx;
new_idx = layer_index * retained_color_indices.get_population ();
if (!c->copy<HBUINT16> (new_idx)) return_trace (false);
}
c->push ();
for (unsigned first_color_index : first_color_index_for_layer)
{
for (hb_codepoint_t color_index : retained_color_indices)
{
if (!c->copy<BGRAColor> (color_records[first_color_index + color_index]))
{
c->pop_discard ();
return_trace (false);
}
}
}
c->add_link (colorRecordsZ, c->pop_pack ());
return_trace (true);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
if (!numPalettes) return_trace (false);
const hb_map_t *color_index_map = &c->plan->colr_palettes;
if (color_index_map->is_empty ()) return_trace (false);
hb_set_t retained_color_indices;
for (const auto _ : color_index_map->keys ())
{
if (_ == 0xFFFF) continue;
retained_color_indices.add (_);
}
if (retained_color_indices.is_empty ()) return_trace (false);
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->version = version;
out->numColors = retained_color_indices.get_population ();
out->numPalettes = numPalettes;
hb_vector_t<unsigned> first_color_index_for_layer;
hb_map_t first_color_to_layer_index;
const hb_array_t<const HBUINT16> colorRecordIndices = colorRecordIndicesZ.as_array (numPalettes);
for (const auto first_color_record_idx : colorRecordIndices)
{
if (first_color_to_layer_index.has (first_color_record_idx)) continue;
first_color_index_for_layer.push (first_color_record_idx);
first_color_to_layer_index.set (first_color_record_idx,
first_color_index_for_layer.length - 1);
}
out->numColorRecords = first_color_index_for_layer.length
* retained_color_indices.get_population ();
const hb_array_t<const BGRAColor> color_records = (this+colorRecordsZ).as_array (numColorRecords);
if (!out->serialize (c->serializer,
colorRecordIndices,
color_records,
first_color_index_for_layer,
first_color_to_layer_index,
retained_color_indices))
return_trace (false);
if (version == 1)
{
hb_barrier ();
return_trace (v1 ().serialize (c->serializer, numPalettes, numColors, this, color_index_map));
}
return_trace (true);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
hb_barrier () &&
(this+colorRecordsZ).sanitize (c, numColorRecords) &&
colorRecordIndicesZ.sanitize (c, numPalettes) &&
(version == 0 || v1 ().sanitize (c, this, numPalettes, numColors)));
}
protected:
HBUINT16 version; /* Table version number */
/* Version 0 */
HBUINT16 numColors; /* Number of colors in each palette. */
HBUINT16 numPalettes; /* Number of palettes in the table. */
HBUINT16 numColorRecords; /* Total number of color records, combined for
* all palettes. */
NNOffset32To<UnsizedArrayOf<BGRAColor>>
colorRecordsZ; /* Offset from the beginning of CPAL table to
* the first ColorRecord. */
UnsizedArrayOf<HBUINT16>
colorRecordIndicesZ; /* Index of each palette’s first color record in
* the combined color record array. */
/*CPALV1Tail v1;*/
public:
DEFINE_SIZE_ARRAY (12, colorRecordIndicesZ);
};
} /* namespace OT */
#endif /* OT_COLOR_CPAL_CPAL_HH */
@@ -0,0 +1,448 @@
/*
* Copyright © 2018 Ebrahim Byagowi
* Copyright © 2020 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Calder Kitagawa
*/
#ifndef OT_COLOR_SBIX_SBIX_HH
#define OT_COLOR_SBIX_SBIX_HH
#include "../../../hb-open-type.hh"
#include "../../../hb-paint.hh"
/*
* sbix -- Standard Bitmap Graphics
* https://docs.microsoft.com/en-us/typography/opentype/spec/sbix
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6sbix.html
*/
#define HB_OT_TAG_sbix HB_TAG('s','b','i','x')
namespace OT {
struct SBIXGlyph
{
SBIXGlyph* copy (hb_serialize_context_t *c, unsigned int data_length) const
{
TRACE_SERIALIZE (this);
SBIXGlyph* new_glyph = c->start_embed<SBIXGlyph> ();
if (unlikely (!c->extend_min (new_glyph))) return_trace (nullptr);
new_glyph->xOffset = xOffset;
new_glyph->yOffset = yOffset;
new_glyph->graphicType = graphicType;
data.copy (c, data_length);
return_trace (new_glyph);
}
HBINT16 xOffset; /* The horizontal (x-axis) offset from the left
* edge of the graphic to the glyph’s origin.
* That is, the x-coordinate of the point on the
* baseline at the left edge of the glyph. */
HBINT16 yOffset; /* The vertical (y-axis) offset from the bottom
* edge of the graphic to the glyph’s origin.
* That is, the y-coordinate of the point on the
* baseline at the left edge of the glyph. */
Tag graphicType; /* Indicates the format of the embedded graphic
* data: one of 'jpg ', 'png ' or 'tiff', or the
* special format 'dupe'. */
UnsizedArrayOf<HBUINT8>
data; /* The actual embedded graphic data. The total
* length is inferred from sequential entries in
* the glyphDataOffsets array and the fixed size
* (8 bytes) of the preceding fields. */
public:
DEFINE_SIZE_ARRAY (8, data);
};
struct SBIXStrike
{
static unsigned int get_size (unsigned num_glyphs)
{ return min_size + num_glyphs * HBUINT32::static_size; }
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
imageOffsetsZ.sanitize_shallow (c, c->get_num_glyphs () + 1));
}
hb_blob_t *get_glyph_blob (unsigned int glyph_id,
hb_blob_t *sbix_blob,
hb_tag_t file_type,
int *x_offset,
int *y_offset,
unsigned int num_glyphs,
unsigned int *strike_ppem) const
{
if (unlikely (!ppem)) return hb_blob_get_empty (); /* To get Null() object out of the way. */
unsigned int retry_count = 8;
unsigned int sbix_len = sbix_blob->length;
unsigned int strike_offset = (const char *) this - (const char *) sbix_blob->data;
assert (strike_offset < sbix_len);
retry:
if (unlikely (glyph_id >= num_glyphs ||
imageOffsetsZ[glyph_id + 1] <= imageOffsetsZ[glyph_id] ||
imageOffsetsZ[glyph_id + 1] - imageOffsetsZ[glyph_id] <= SBIXGlyph::min_size ||
(unsigned int) imageOffsetsZ[glyph_id + 1] > sbix_len - strike_offset))
return hb_blob_get_empty ();
unsigned int glyph_offset = strike_offset + (unsigned int) imageOffsetsZ[glyph_id] + SBIXGlyph::min_size;
unsigned int glyph_length = imageOffsetsZ[glyph_id + 1] - imageOffsetsZ[glyph_id] - SBIXGlyph::min_size;
const SBIXGlyph *glyph = &(this+imageOffsetsZ[glyph_id]);
if (glyph->graphicType == HB_TAG ('d','u','p','e'))
{
if (glyph_length >= 2)
{
glyph_id = *((HBUINT16 *) &glyph->data);
if (retry_count--)
goto retry;
}
return hb_blob_get_empty ();
}
if (unlikely (file_type != glyph->graphicType))
return hb_blob_get_empty ();
if (strike_ppem) *strike_ppem = ppem;
if (x_offset) *x_offset = glyph->xOffset;
if (y_offset) *y_offset = glyph->yOffset;
return hb_blob_create_sub_blob (sbix_blob, glyph_offset, glyph_length);
}
bool subset (hb_subset_context_t *c, unsigned int available_len) const
{
TRACE_SUBSET (this);
unsigned int num_output_glyphs = c->plan->num_output_glyphs ();
auto* out = c->serializer->start_embed<SBIXStrike> ();
auto snap = c->serializer->snapshot ();
if (unlikely (!c->serializer->extend (out, num_output_glyphs + 1))) return_trace (false);
out->ppem = ppem;
out->resolution = resolution;
HBUINT32 head;
head = get_size (num_output_glyphs + 1);
bool has_glyphs = false;
for (unsigned new_gid = 0; new_gid < num_output_glyphs; new_gid++)
{
hb_codepoint_t old_gid;
if (!c->plan->old_gid_for_new_gid (new_gid, &old_gid) ||
unlikely (imageOffsetsZ[old_gid].is_null () ||
imageOffsetsZ[old_gid + 1].is_null () ||
imageOffsetsZ[old_gid + 1] <= imageOffsetsZ[old_gid] ||
imageOffsetsZ[old_gid + 1] - imageOffsetsZ[old_gid] <= SBIXGlyph::min_size) ||
(unsigned int) imageOffsetsZ[old_gid + 1] > available_len)
{
out->imageOffsetsZ[new_gid] = head;
continue;
}
has_glyphs = true;
unsigned int delta = imageOffsetsZ[old_gid + 1] - imageOffsetsZ[old_gid];
unsigned int glyph_data_length = delta - SBIXGlyph::min_size;
if (!(this+imageOffsetsZ[old_gid]).copy (c->serializer, glyph_data_length))
return_trace (false);
out->imageOffsetsZ[new_gid] = head;
head += delta;
}
if (has_glyphs)
out->imageOffsetsZ[num_output_glyphs] = head;
else
c->serializer->revert (snap);
return_trace (has_glyphs);
}
public:
HBUINT16 ppem; /* The PPEM size for which this strike was designed. */
HBUINT16 resolution; /* The device pixel density (in PPI) for which this
* strike was designed. (E.g., 96 PPI, 192 PPI.) */
protected:
UnsizedArrayOf<Offset32To<SBIXGlyph>>
imageOffsetsZ; /* Offset from the beginning of the strike data header
* to bitmap data for an individual glyph ID. */
public:
DEFINE_SIZE_ARRAY (4, imageOffsetsZ);
};
struct sbix
{
static constexpr hb_tag_t tableTag = HB_OT_TAG_sbix;
bool has_data () const { return version; }
const SBIXStrike &get_strike (unsigned int i) const { return this+strikes[i]; }
struct accelerator_t
{
accelerator_t (hb_face_t *face)
{
table = hb_sanitize_context_t ().reference_table<sbix> (face);
num_glyphs = face->get_num_glyphs ();
}
~accelerator_t () { table.destroy (); }
bool has_data () const { return table->has_data (); }
bool get_extents (hb_font_t *font,
hb_codepoint_t glyph,
hb_glyph_extents_t *extents,
bool scale = true) const
{
/* We only support PNG right now, and following function checks type. */
return get_png_extents (font, glyph, extents, scale);
}
hb_blob_t *reference_png (hb_font_t *font,
hb_codepoint_t glyph_id,
int *x_offset,
int *y_offset,
unsigned int *available_ppem) const
{
return choose_strike (font).get_glyph_blob (glyph_id, table.get_blob (),
HB_TAG ('p','n','g',' '),
x_offset, y_offset,
num_glyphs, available_ppem);
}
bool paint_glyph (hb_font_t *font, hb_codepoint_t glyph, hb_paint_funcs_t *funcs, void *data) const
{
if (!has_data ())
return false;
int x_offset = 0, y_offset = 0;
unsigned int strike_ppem = 0;
hb_blob_t *blob = reference_png (font, glyph, &x_offset, &y_offset, &strike_ppem);
hb_glyph_extents_t extents;
hb_glyph_extents_t pixel_extents;
if (blob == hb_blob_get_empty ())
return false;
if (!hb_font_get_glyph_extents (font, glyph, &extents))
return false;
if (unlikely (!get_extents (font, glyph, &pixel_extents, false)))
return false;
bool ret = funcs->image (data,
blob,
pixel_extents.width, -pixel_extents.height,
HB_PAINT_IMAGE_FORMAT_PNG,
font->slant_xy,
&extents);
hb_blob_destroy (blob);
return ret;
}
private:
const SBIXStrike &choose_strike (hb_font_t *font) const
{
unsigned count = table->strikes.len;
if (unlikely (!count))
return Null (SBIXStrike);
unsigned int requested_ppem = hb_max (font->x_ppem, font->y_ppem);
if (!requested_ppem)
requested_ppem = 1<<30; /* Choose largest strike. */
/* TODO Add DPI sensitivity as well? */
unsigned int best_i = 0;
unsigned int best_ppem = table->get_strike (0).ppem;
for (unsigned int i = 1; i < count; i++)
{
unsigned int ppem = (table->get_strike (i)).ppem;
if ((requested_ppem <= ppem && ppem < best_ppem) ||
(requested_ppem > best_ppem && ppem > best_ppem))
{
best_i = i;
best_ppem = ppem;
}
}
return table->get_strike (best_i);
}
struct PNGHeader
{
HBUINT8 signature[8];
struct
{
struct
{
HBUINT32 length;
Tag type;
} header;
HBUINT32 width;
HBUINT32 height;
HBUINT8 bitDepth;
HBUINT8 colorType;
HBUINT8 compressionMethod;
HBUINT8 filterMethod;
HBUINT8 interlaceMethod;
} IHDR;
public:
DEFINE_SIZE_STATIC (29);
};
bool get_png_extents (hb_font_t *font,
hb_codepoint_t glyph,
hb_glyph_extents_t *extents,
bool scale = true) const
{
/* Following code is safe to call even without data.
* But faster to short-circuit. */
if (!has_data ())
return false;
int x_offset = 0, y_offset = 0;
unsigned int strike_ppem = 0;
hb_blob_t *blob = reference_png (font, glyph, &x_offset, &y_offset, &strike_ppem);
const PNGHeader &png = *blob->as<PNGHeader>();
if (png.IHDR.height >= 65536 || png.IHDR.width >= 65536)
{
hb_blob_destroy (blob);
return false;
}
extents->x_bearing = x_offset;
extents->y_bearing = png.IHDR.height + y_offset;
extents->width = png.IHDR.width;
extents->height = -1 * png.IHDR.height;
/* Convert to font units. */
if (strike_ppem && scale)
{
float scale = font->face->get_upem () / (float) strike_ppem;
extents->x_bearing = roundf (extents->x_bearing * scale);
extents->y_bearing = roundf (extents->y_bearing * scale);
extents->width = roundf (extents->width * scale);
extents->height = roundf (extents->height * scale);
}
if (scale)
font->scale_glyph_extents (extents);
hb_blob_destroy (blob);
return strike_ppem;
}
private:
hb_blob_ptr_t<sbix> table;
unsigned int num_glyphs;
};
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (likely (c->check_struct (this) &&
hb_barrier () &&
version >= 1 &&
strikes.sanitize (c, this)));
}
bool
add_strike (hb_subset_context_t *c, unsigned i) const
{
if (strikes[i].is_null () || c->source_blob->length < (unsigned) strikes[i])
return false;
return (this+strikes[i]).subset (c, c->source_blob->length - (unsigned) strikes[i]);
}
bool serialize_strike_offsets (hb_subset_context_t *c) const
{
TRACE_SERIALIZE (this);
auto *out = c->serializer->start_embed<Array32OfOffset32To<SBIXStrike>> ();
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
hb_vector_t<Offset32To<SBIXStrike>*> new_strikes;
hb_vector_t<hb_serialize_context_t::objidx_t> objidxs;
for (int i = strikes.len - 1; i >= 0; --i)
{
auto* o = out->serialize_append (c->serializer);
if (unlikely (!o)) return_trace (false);
*o = 0;
auto snap = c->serializer->snapshot ();
c->serializer->push ();
bool ret = add_strike (c, i);
if (!ret)
{
c->serializer->pop_discard ();
out->pop ();
c->serializer->revert (snap);
}
else
{
objidxs.push (c->serializer->pop_pack ());
new_strikes.push (o);
}
}
for (unsigned int i = 0; i < new_strikes.length; ++i)
c->serializer->add_link (*new_strikes[i], objidxs[new_strikes.length - 1 - i]);
return_trace (true);
}
bool subset (hb_subset_context_t* c) const
{
TRACE_SUBSET (this);
if (unlikely (!c->serializer->embed (this->version))) return_trace (false);
if (unlikely (!c->serializer->embed (this->flags))) return_trace (false);
return_trace (serialize_strike_offsets (c));
}
protected:
HBUINT16 version; /* Table version number — set to 1 */
HBUINT16 flags; /* Bit 0: Set to 1. Bit 1: Draw outlines.
* Bits 2 to 15: reserved (set to 0). */
Array32OfOffset32To<SBIXStrike>
strikes; /* Offsets from the beginning of the 'sbix'
* table to data for each individual bitmap strike. */
public:
DEFINE_SIZE_ARRAY (8, strikes);
};
struct sbix_accelerator_t : sbix::accelerator_t {
sbix_accelerator_t (hb_face_t *face) : sbix::accelerator_t (face) {}
};
} /* namespace OT */
#endif /* OT_COLOR_SBIX_SBIX_HH */
@@ -0,0 +1,152 @@
/*
* Copyright © 2018 Ebrahim Byagowi
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/
#ifndef OT_COLOR_SVG_SVG_HH
#define OT_COLOR_SVG_SVG_HH
#include "../../../hb-open-type.hh"
#include "../../../hb-blob.hh"
#include "../../../hb-paint.hh"
/*
* SVG -- SVG (Scalable Vector Graphics)
* https://docs.microsoft.com/en-us/typography/opentype/spec/svg
*/
#define HB_OT_TAG_SVG HB_TAG('S','V','G',' ')
namespace OT {
struct SVGDocumentIndexEntry
{
int cmp (hb_codepoint_t g) const
{ return g < startGlyphID ? -1 : g > endGlyphID ? 1 : 0; }
hb_blob_t *reference_blob (hb_blob_t *svg_blob, unsigned int index_offset) const
{
return hb_blob_create_sub_blob (svg_blob,
index_offset + (unsigned int) svgDoc,
svgDocLength);
}
bool sanitize (hb_sanitize_context_t *c, const void *base) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
hb_barrier () &&
svgDoc.sanitize (c, base, svgDocLength));
}
protected:
HBUINT16 startGlyphID; /* The first glyph ID in the range described by
* this index entry. */
HBUINT16 endGlyphID; /* The last glyph ID in the range described by
* this index entry. Must be >= startGlyphID. */
NNOffset32To<UnsizedArrayOf<HBUINT8>>
svgDoc; /* Offset from the beginning of the SVG Document Index
* to an SVG document. Must be non-zero. */
HBUINT32 svgDocLength; /* Length of the SVG document.
* Must be non-zero. */
public:
DEFINE_SIZE_STATIC (12);
};
struct SVG
{
static constexpr hb_tag_t tableTag = HB_OT_TAG_SVG;
bool has_data () const { return svgDocEntries; }
struct accelerator_t
{
accelerator_t (hb_face_t *face)
{ table = hb_sanitize_context_t ().reference_table<SVG> (face); }
~accelerator_t () { table.destroy (); }
hb_blob_t *reference_blob_for_glyph (hb_codepoint_t glyph_id) const
{
return table->get_glyph_entry (glyph_id).reference_blob (table.get_blob (),
table->svgDocEntries);
}
bool has_data () const { return table->has_data (); }
bool paint_glyph (hb_font_t *font HB_UNUSED, hb_codepoint_t glyph, hb_paint_funcs_t *funcs, void *data) const
{
if (!has_data ())
return false;
hb_blob_t *blob = reference_blob_for_glyph (glyph);
if (blob == hb_blob_get_empty ())
return false;
funcs->image (data,
blob,
0, 0,
HB_PAINT_IMAGE_FORMAT_SVG,
font->slant_xy,
nullptr);
hb_blob_destroy (blob);
return true;
}
private:
hb_blob_ptr_t<SVG> table;
public:
DEFINE_SIZE_STATIC (sizeof (hb_blob_ptr_t<SVG>));
};
const SVGDocumentIndexEntry &get_glyph_entry (hb_codepoint_t glyph_id) const
{ return (this+svgDocEntries).bsearch (glyph_id); }
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (likely (c->check_struct (this) &&
(this+svgDocEntries).sanitize_shallow (c)));
}
protected:
HBUINT16 version; /* Table version (starting at 0). */
Offset32To<SortedArray16Of<SVGDocumentIndexEntry>>
svgDocEntries; /* Offset (relative to the start of the SVG table) to the
* SVG Documents Index. Must be non-zero. */
/* Array of SVG Document Index Entries. */
HBUINT32 reserved; /* Set to 0. */
public:
DEFINE_SIZE_STATIC (10);
};
struct SVG_accelerator_t : SVG::accelerator_t {
SVG_accelerator_t (hb_face_t *face) : SVG::accelerator_t (face) {}
};
} /* namespace OT */
#endif /* OT_COLOR_SVG_SVG_HH */
@@ -0,0 +1,352 @@
/*
* Copyright © 2007,2008,2009 Red Hat, Inc.
* Copyright © 2010,2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod, Garret Rieger
*/
#ifndef OT_LAYOUT_COMMON_COVERAGE_HH
#define OT_LAYOUT_COMMON_COVERAGE_HH
#include "../types.hh"
#include "CoverageFormat1.hh"
#include "CoverageFormat2.hh"
namespace OT {
namespace Layout {
namespace Common {
template<typename Iterator>
static inline void Coverage_serialize (hb_serialize_context_t *c,
Iterator it);
struct Coverage
{
protected:
union {
HBUINT16 format; /* Format identifier */
CoverageFormat1_3<SmallTypes> format1;
CoverageFormat2_4<SmallTypes> format2;
#ifndef HB_NO_BEYOND_64K
CoverageFormat1_3<MediumTypes>format3;
CoverageFormat2_4<MediumTypes>format4;
#endif
} u;
public:
DEFINE_SIZE_UNION (2, format);
#ifndef HB_OPTIMIZE_SIZE
HB_ALWAYS_INLINE
#endif
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format)
{
case 1: return_trace (u.format1.sanitize (c));
case 2: return_trace (u.format2.sanitize (c));
#ifndef HB_NO_BEYOND_64K
case 3: return_trace (u.format3.sanitize (c));
case 4: return_trace (u.format4.sanitize (c));
#endif
default:return_trace (true);
}
}
/* Has interface. */
unsigned operator [] (hb_codepoint_t k) const { return get (k); }
bool has (hb_codepoint_t k) const { return (*this)[k] != NOT_COVERED; }
/* Predicate. */
bool operator () (hb_codepoint_t k) const { return has (k); }
unsigned int get (hb_codepoint_t k) const { return get_coverage (k); }
unsigned int get_coverage (hb_codepoint_t glyph_id) const
{
switch (u.format) {
case 1: return u.format1.get_coverage (glyph_id);
case 2: return u.format2.get_coverage (glyph_id);
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.get_coverage (glyph_id);
case 4: return u.format4.get_coverage (glyph_id);
#endif
default:return NOT_COVERED;
}
}
unsigned get_population () const
{
switch (u.format) {
case 1: return u.format1.get_population ();
case 2: return u.format2.get_population ();
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.get_population ();
case 4: return u.format4.get_population ();
#endif
default:return NOT_COVERED;
}
}
template <typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
unsigned count = hb_len (glyphs);
unsigned num_ranges = 0;
hb_codepoint_t last = (hb_codepoint_t) -2;
hb_codepoint_t max = 0;
bool unsorted = false;
for (auto g: glyphs)
{
if (last != (hb_codepoint_t) -2 && g < last)
unsorted = true;
if (last + 1 != g)
num_ranges++;
last = g;
if (g > max) max = g;
}
u.format = !unsorted && count <= num_ranges * 3 ? 1 : 2;
#ifndef HB_NO_BEYOND_64K
if (max > 0xFFFFu)
u.format += 2;
if (unlikely (max > 0xFFFFFFu))
#else
if (unlikely (max > 0xFFFFu))
#endif
{
c->check_success (false, HB_SERIALIZE_ERROR_INT_OVERFLOW);
return_trace (false);
}
switch (u.format)
{
case 1: return_trace (u.format1.serialize (c, glyphs));
case 2: return_trace (u.format2.serialize (c, glyphs));
#ifndef HB_NO_BEYOND_64K
case 3: return_trace (u.format3.serialize (c, glyphs));
case 4: return_trace (u.format4.serialize (c, glyphs));
#endif
default:return_trace (false);
}
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
auto it =
+ iter ()
| hb_take (c->plan->source->get_num_glyphs ())
| hb_map_retains_sorting (c->plan->glyph_map_gsub)
| hb_filter ([] (hb_codepoint_t glyph) { return glyph != HB_MAP_VALUE_INVALID; })
;
// Cache the iterator result as it will be iterated multiple times
// by the serialize code below.
hb_sorted_vector_t<hb_codepoint_t> glyphs (it);
Coverage_serialize (c->serializer, glyphs.iter ());
return_trace (bool (glyphs));
}
bool intersects (const hb_set_t *glyphs) const
{
switch (u.format)
{
case 1: return u.format1.intersects (glyphs);
case 2: return u.format2.intersects (glyphs);
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.intersects (glyphs);
case 4: return u.format4.intersects (glyphs);
#endif
default:return false;
}
}
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
{
switch (u.format)
{
case 1: return u.format1.intersects_coverage (glyphs, index);
case 2: return u.format2.intersects_coverage (glyphs, index);
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.intersects_coverage (glyphs, index);
case 4: return u.format4.intersects_coverage (glyphs, index);
#endif
default:return false;
}
}
/* Might return false if array looks unsorted.
* Used for faster rejection of corrupt data. */
template <typename set_t>
bool collect_coverage (set_t *glyphs) const
{
switch (u.format)
{
case 1: return u.format1.collect_coverage (glyphs);
case 2: return u.format2.collect_coverage (glyphs);
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.collect_coverage (glyphs);
case 4: return u.format4.collect_coverage (glyphs);
#endif
default:return false;
}
}
template <typename IterableOut,
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
{
switch (u.format)
{
case 1: return u.format1.intersect_set (glyphs, intersect_glyphs);
case 2: return u.format2.intersect_set (glyphs, intersect_glyphs);
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.intersect_set (glyphs, intersect_glyphs);
case 4: return u.format4.intersect_set (glyphs, intersect_glyphs);
#endif
default:return ;
}
}
struct iter_t : hb_iter_with_fallback_t<iter_t, hb_codepoint_t>
{
static constexpr bool is_sorted_iterator = true;
iter_t (const Coverage &c_ = Null (Coverage))
{
hb_memset (this, 0, sizeof (*this));
format = c_.u.format;
switch (format)
{
case 1: u.format1.init (c_.u.format1); return;
case 2: u.format2.init (c_.u.format2); return;
#ifndef HB_NO_BEYOND_64K
case 3: u.format3.init (c_.u.format3); return;
case 4: u.format4.init (c_.u.format4); return;
#endif
default: return;
}
}
bool __more__ () const
{
switch (format)
{
case 1: return u.format1.__more__ ();
case 2: return u.format2.__more__ ();
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.__more__ ();
case 4: return u.format4.__more__ ();
#endif
default:return false;
}
}
void __next__ ()
{
switch (format)
{
case 1: u.format1.__next__ (); break;
case 2: u.format2.__next__ (); break;
#ifndef HB_NO_BEYOND_64K
case 3: u.format3.__next__ (); break;
case 4: u.format4.__next__ (); break;
#endif
default: break;
}
}
typedef hb_codepoint_t __item_t__;
__item_t__ __item__ () const { return get_glyph (); }
hb_codepoint_t get_glyph () const
{
switch (format)
{
case 1: return u.format1.get_glyph ();
case 2: return u.format2.get_glyph ();
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3.get_glyph ();
case 4: return u.format4.get_glyph ();
#endif
default:return 0;
}
}
bool operator != (const iter_t& o) const
{
if (unlikely (format != o.format)) return true;
switch (format)
{
case 1: return u.format1 != o.u.format1;
case 2: return u.format2 != o.u.format2;
#ifndef HB_NO_BEYOND_64K
case 3: return u.format3 != o.u.format3;
case 4: return u.format4 != o.u.format4;
#endif
default:return false;
}
}
iter_t __end__ () const
{
iter_t it = {};
it.format = format;
switch (format)
{
case 1: it.u.format1 = u.format1.__end__ (); break;
case 2: it.u.format2 = u.format2.__end__ (); break;
#ifndef HB_NO_BEYOND_64K
case 3: it.u.format3 = u.format3.__end__ (); break;
case 4: it.u.format4 = u.format4.__end__ (); break;
#endif
default: break;
}
return it;
}
private:
unsigned int format;
union {
#ifndef HB_NO_BEYOND_64K
CoverageFormat2_4<MediumTypes>::iter_t format4; /* Put this one first since it's larger; helps shut up compiler. */
CoverageFormat1_3<MediumTypes>::iter_t format3;
#endif
CoverageFormat2_4<SmallTypes>::iter_t format2; /* Put this one first since it's larger; helps shut up compiler. */
CoverageFormat1_3<SmallTypes>::iter_t format1;
} u;
};
iter_t iter () const { return iter_t (*this); }
};
template<typename Iterator>
static inline void
Coverage_serialize (hb_serialize_context_t *c,
Iterator it)
{ c->start_embed<Coverage> ()->serialize (c, it); }
}
}
}
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGE_HH
@@ -0,0 +1,133 @@
/*
* Copyright © 2007,2008,2009 Red Hat, Inc.
* Copyright © 2010,2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod, Garret Rieger
*/
#ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
#define OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
namespace OT {
namespace Layout {
namespace Common {
#define NOT_COVERED ((unsigned int) -1)
template <typename Types>
struct CoverageFormat1_3
{
friend struct Coverage;
protected:
HBUINT16 coverageFormat; /* Format identifier--format = 1 */
SortedArray16Of<typename Types::HBGlyphID>
glyphArray; /* Array of GlyphIDs--in numerical order */
public:
DEFINE_SIZE_ARRAY (4, glyphArray);
private:
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (glyphArray.sanitize (c));
}
unsigned int get_coverage (hb_codepoint_t glyph_id) const
{
unsigned int i;
glyphArray.bfind (glyph_id, &i, HB_NOT_FOUND_STORE, NOT_COVERED);
return i;
}
unsigned get_population () const
{
return glyphArray.len;
}
template <typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
{
TRACE_SERIALIZE (this);
return_trace (glyphArray.serialize (c, glyphs));
}
bool intersects (const hb_set_t *glyphs) const
{
if (glyphArray.len > glyphs->get_population () * hb_bit_storage ((unsigned) glyphArray.len) / 2)
{
for (auto g : *glyphs)
if (get_coverage (g) != NOT_COVERED)
return true;
return false;
}
for (const auto& g : glyphArray.as_array ())
if (glyphs->has (g))
return true;
return false;
}
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
{ return glyphs->has (glyphArray[index]); }
template <typename IterableOut,
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
{
unsigned count = glyphArray.len;
for (unsigned i = 0; i < count; i++)
if (glyphs.has (glyphArray[i]))
intersect_glyphs << glyphArray[i];
}
template <typename set_t>
bool collect_coverage (set_t *glyphs) const
{ return glyphs->add_sorted_array (glyphArray.as_array ()); }
public:
/* Older compilers need this to be public. */
struct iter_t
{
void init (const struct CoverageFormat1_3 &c_) { c = &c_; i = 0; }
bool __more__ () const { return i < c->glyphArray.len; }
void __next__ () { i++; }
hb_codepoint_t get_glyph () const { return c->glyphArray[i]; }
bool operator != (const iter_t& o) const
{ return i != o.i; }
iter_t __end__ () const { iter_t it; it.init (*c); it.i = c->glyphArray.len; return it; }
private:
const struct CoverageFormat1_3 *c;
unsigned int i;
};
private:
};
}
}
}
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
@@ -0,0 +1,239 @@
/*
* Copyright © 2007,2008,2009 Red Hat, Inc.
* Copyright © 2010,2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod, Garret Rieger
*/
#ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
#define OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
#include "RangeRecord.hh"
namespace OT {
namespace Layout {
namespace Common {
template <typename Types>
struct CoverageFormat2_4
{
friend struct Coverage;
protected:
HBUINT16 coverageFormat; /* Format identifier--format = 2 */
SortedArray16Of<RangeRecord<Types>>
rangeRecord; /* Array of glyph ranges--ordered by
* Start GlyphID. rangeCount entries
* long */
public:
DEFINE_SIZE_ARRAY (4, rangeRecord);
private:
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (rangeRecord.sanitize (c));
}
unsigned int get_coverage (hb_codepoint_t glyph_id) const
{
const RangeRecord<Types> &range = rangeRecord.bsearch (glyph_id);
return likely (range.first <= range.last)
? (unsigned int) range.value + (glyph_id - range.first)
: NOT_COVERED;
}
unsigned get_population () const
{
typename Types::large_int ret = 0;
for (const auto &r : rangeRecord)
ret += r.get_population ();
return ret > UINT_MAX ? UINT_MAX : (unsigned) ret;
}
template <typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
unsigned num_ranges = 0;
hb_codepoint_t last = (hb_codepoint_t) -2;
for (auto g: glyphs)
{
if (last + 1 != g)
num_ranges++;
last = g;
}
if (unlikely (!rangeRecord.serialize (c, num_ranges))) return_trace (false);
if (!num_ranges) return_trace (true);
unsigned count = 0;
unsigned range = (unsigned) -1;
last = (hb_codepoint_t) -2;
unsigned unsorted = false;
for (auto g: glyphs)
{
if (last + 1 != g)
{
if (unlikely (last != (hb_codepoint_t) -2 && last + 1 > g))
unsorted = true;
range++;
rangeRecord.arrayZ[range].first = g;
rangeRecord.arrayZ[range].value = count;
}
rangeRecord.arrayZ[range].last = g;
last = g;
count++;
}
if (unlikely (unsorted))
rangeRecord.as_array ().qsort (RangeRecord<Types>::cmp_range);
return_trace (true);
}
bool intersects (const hb_set_t *glyphs) const
{
if (rangeRecord.len > glyphs->get_population () * hb_bit_storage ((unsigned) rangeRecord.len) / 2)
{
for (auto g : *glyphs)
if (get_coverage (g) != NOT_COVERED)
return true;
return false;
}
return hb_any (+ hb_iter (rangeRecord)
| hb_map ([glyphs] (const RangeRecord<Types> &range) { return range.intersects (*glyphs); }));
}
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
{
auto *range = rangeRecord.as_array ().bsearch (index);
if (range)
return range->intersects (*glyphs);
return false;
}
template <typename IterableOut,
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
{
/* Break out of loop for overlapping, broken, tables,
* to avoid fuzzer timouts. */
hb_codepoint_t last = 0;
for (const auto& range : rangeRecord)
{
if (unlikely (range.first < last))
break;
last = range.last;
for (hb_codepoint_t g = range.first - 1;
glyphs.next (&g) && g <= last;)
intersect_glyphs << g;
}
}
template <typename set_t>
bool collect_coverage (set_t *glyphs) const
{
for (const auto& range: rangeRecord)
if (unlikely (!range.collect_coverage (glyphs)))
return false;
return true;
}
public:
/* Older compilers need this to be public. */
struct iter_t
{
void init (const CoverageFormat2_4 &c_)
{
c = &c_;
coverage = 0;
i = 0;
j = c->rangeRecord.len ? c->rangeRecord[0].first : 0;
if (unlikely (c->rangeRecord[0].first > c->rangeRecord[0].last))
{
/* Broken table. Skip. */
i = c->rangeRecord.len;
j = 0;
}
}
bool __more__ () const { return i < c->rangeRecord.len; }
void __next__ ()
{
if (j >= c->rangeRecord[i].last)
{
i++;
if (__more__ ())
{
unsigned int old = coverage;
j = c->rangeRecord.arrayZ[i].first;
coverage = c->rangeRecord.arrayZ[i].value;
if (unlikely (coverage != old + 1))
{
/* Broken table. Skip. Important to avoid DoS.
* Also, our callers depend on coverage being
* consecutive and monotonically increasing,
* ie. iota(). */
i = c->rangeRecord.len;
j = 0;
return;
}
}
else
j = 0;
return;
}
coverage++;
j++;
}
hb_codepoint_t get_glyph () const { return j; }
bool operator != (const iter_t& o) const
{ return i != o.i || j != o.j; }
iter_t __end__ () const
{
iter_t it;
it.init (*c);
it.i = c->rangeRecord.len;
it.j = 0;
return it;
}
private:
const struct CoverageFormat2_4 *c;
unsigned int i, coverage;
hb_codepoint_t j;
};
private:
};
}
}
}
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
@@ -0,0 +1,97 @@
/*
* Copyright © 2007,2008,2009 Red Hat, Inc.
* Copyright © 2010,2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod, Garret Rieger
*/
#ifndef OT_LAYOUT_COMMON_RANGERECORD_HH
#define OT_LAYOUT_COMMON_RANGERECORD_HH
namespace OT {
namespace Layout {
namespace Common {
template <typename Types>
struct RangeRecord
{
typename Types::HBGlyphID first; /* First GlyphID in the range */
typename Types::HBGlyphID last; /* Last GlyphID in the range */
HBUINT16 value; /* Value */
DEFINE_SIZE_STATIC (2 + 2 * Types::size);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
}
int cmp (hb_codepoint_t g) const
{ return g < first ? -1 : g <= last ? 0 : +1; }
HB_INTERNAL static int cmp_range (const void *pa, const void *pb) {
const RangeRecord *a = (const RangeRecord *) pa;
const RangeRecord *b = (const RangeRecord *) pb;
if (a->first < b->first) return -1;
if (a->first > b->first) return +1;
if (a->last < b->last) return -1;
if (a->last > b->last) return +1;
if (a->value < b->value) return -1;
if (a->value > b->value) return +1;
return 0;
}
unsigned get_population () const
{
if (unlikely (last < first)) return 0;
return (last - first + 1);
}
bool intersects (const hb_set_t &glyphs) const
{ return glyphs.intersects (first, last); }
template <typename set_t>
bool collect_coverage (set_t *glyphs) const
{ return glyphs->add_range (first, last); }
};
}
}
}
// TODO(garretrieger): This was previously implemented using
// DECLARE_NULL_NAMESPACE_BYTES_TEMPLATE1 (OT, RangeRecord, 9);
// but that only works when there is only a single namespace level.
// The macro should probably be fixed so it can work in this situation.
extern HB_INTERNAL const unsigned char _hb_Null_OT_RangeRecord[9];
template <typename Spec>
struct Null<OT::Layout::Common::RangeRecord<Spec>> {
static OT::Layout::Common::RangeRecord<Spec> const & get_null () {
return *reinterpret_cast<const OT::Layout::Common::RangeRecord<Spec> *> (_hb_Null_OT_RangeRecord);
}
};
#endif // #ifndef OT_LAYOUT_COMMON_RANGERECORD_HH
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
#ifndef OT_LAYOUT_GPOS_ANCHOR_HH
#define OT_LAYOUT_GPOS_ANCHOR_HH
#include "AnchorFormat1.hh"
#include "AnchorFormat2.hh"
#include "AnchorFormat3.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct Anchor
{
protected:
union {
HBUINT16 format; /* Format identifier */
AnchorFormat1 format1;
AnchorFormat2 format2;
AnchorFormat3 format3;
} u;
public:
DEFINE_SIZE_UNION (2, format);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
case 1: return_trace (u.format1.sanitize (c));
case 2: return_trace (u.format2.sanitize (c));
case 3: return_trace (u.format3.sanitize (c));
default:return_trace (true);
}
}
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id,
float *x, float *y) const
{
*x = *y = 0;
switch (u.format) {
case 1: u.format1.get_anchor (c, glyph_id, x, y); return;
case 2: u.format2.get_anchor (c, glyph_id, x, y); return;
case 3: u.format3.get_anchor (c, glyph_id, x, y); return;
default: return;
}
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
switch (u.format) {
case 1: return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
case 2:
if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
{
// AnchorFormat 2 just containins extra hinting information, so
// if hints are being dropped convert to format 1.
return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
}
return_trace (bool (reinterpret_cast<Anchor *> (u.format2.copy (c->serializer))));
case 3: return_trace (u.format3.subset (c));
default:return_trace (false);
}
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
switch (u.format) {
case 1: case 2:
return;
case 3:
u.format3.collect_variation_indices (c);
return;
default: return;
}
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_ANCHOR_HH
@@ -0,0 +1,46 @@
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
#define OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct AnchorFormat1
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
FWORD xCoordinate; /* Horizontal value--in design units */
FWORD yCoordinate; /* Vertical value--in design units */
public:
DEFINE_SIZE_STATIC (6);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
}
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id HB_UNUSED,
float *x, float *y) const
{
hb_font_t *font = c->font;
*x = font->em_fscale_x (xCoordinate);
*y = font->em_fscale_y (yCoordinate);
}
AnchorFormat1* copy (hb_serialize_context_t *c) const
{
TRACE_SERIALIZE (this);
AnchorFormat1* out = c->embed<AnchorFormat1> (this);
if (!out) return_trace (out);
out->format = 1;
return_trace (out);
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
@@ -0,0 +1,58 @@
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
#define OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct AnchorFormat2
{
protected:
HBUINT16 format; /* Format identifier--format = 2 */
FWORD xCoordinate; /* Horizontal value--in design units */
FWORD yCoordinate; /* Vertical value--in design units */
HBUINT16 anchorPoint; /* Index to glyph contour point */
public:
DEFINE_SIZE_STATIC (8);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
}
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id,
float *x, float *y) const
{
hb_font_t *font = c->font;
#ifdef HB_NO_HINTING
*x = font->em_fscale_x (xCoordinate);
*y = font->em_fscale_y (yCoordinate);
return;
#endif
unsigned int x_ppem = font->x_ppem;
unsigned int y_ppem = font->y_ppem;
hb_position_t cx = 0, cy = 0;
bool ret;
ret = (x_ppem || y_ppem) &&
font->get_glyph_contour_point_for_origin (glyph_id, anchorPoint, HB_DIRECTION_LTR, &cx, &cy);
*x = ret && x_ppem ? cx : font->em_fscale_x (xCoordinate);
*y = ret && y_ppem ? cy : font->em_fscale_y (yCoordinate);
}
AnchorFormat2* copy (hb_serialize_context_t *c) const
{
TRACE_SERIALIZE (this);
return_trace (c->embed<AnchorFormat2> (this));
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
@@ -0,0 +1,120 @@
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
#define OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct AnchorFormat3
{
protected:
HBUINT16 format; /* Format identifier--format = 3 */
FWORD xCoordinate; /* Horizontal value--in design units */
FWORD yCoordinate; /* Vertical value--in design units */
Offset16To<Device>
xDeviceTable; /* Offset to Device table for X
* coordinate-- from beginning of
* Anchor table (may be NULL) */
Offset16To<Device>
yDeviceTable; /* Offset to Device table for Y
* coordinate-- from beginning of
* Anchor table (may be NULL) */
public:
DEFINE_SIZE_STATIC (10);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (unlikely (!c->check_struct (this))) return_trace (false);
return_trace (xDeviceTable.sanitize (c, this) && yDeviceTable.sanitize (c, this));
}
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id HB_UNUSED,
float *x, float *y) const
{
hb_font_t *font = c->font;
*x = font->em_fscale_x (xCoordinate);
*y = font->em_fscale_y (yCoordinate);
if ((font->x_ppem || font->num_coords) && xDeviceTable.sanitize (&c->sanitizer, this))
{
hb_barrier ();
*x += (this+xDeviceTable).get_x_delta (font, c->var_store, c->var_store_cache);
}
if ((font->y_ppem || font->num_coords) && yDeviceTable.sanitize (&c->sanitizer, this))
{
hb_barrier ();
*y += (this+yDeviceTable).get_y_delta (font, c->var_store, c->var_store_cache);
}
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->embed (format))) return_trace (false);
if (unlikely (!c->serializer->embed (xCoordinate))) return_trace (false);
if (unlikely (!c->serializer->embed (yCoordinate))) return_trace (false);
unsigned x_varidx = xDeviceTable ? (this+xDeviceTable).get_variation_index () : HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
if (x_varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
{
hb_pair_t<unsigned, int> *new_varidx_delta;
if (!c->plan->layout_variation_idx_delta_map.has (x_varidx, &new_varidx_delta))
return_trace (false);
x_varidx = hb_first (*new_varidx_delta);
int delta = hb_second (*new_varidx_delta);
if (delta != 0)
{
if (!c->serializer->check_assign (out->xCoordinate, xCoordinate + delta,
HB_SERIALIZE_ERROR_INT_OVERFLOW))
return_trace (false);
}
}
unsigned y_varidx = yDeviceTable ? (this+yDeviceTable).get_variation_index () : HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
if (y_varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
{
hb_pair_t<unsigned, int> *new_varidx_delta;
if (!c->plan->layout_variation_idx_delta_map.has (y_varidx, &new_varidx_delta))
return_trace (false);
y_varidx = hb_first (*new_varidx_delta);
int delta = hb_second (*new_varidx_delta);
if (delta != 0)
{
if (!c->serializer->check_assign (out->yCoordinate, yCoordinate + delta,
HB_SERIALIZE_ERROR_INT_OVERFLOW))
return_trace (false);
}
}
/* in case that all axes are pinned or no variations after instantiation,
* both var_idxes will be mapped to HB_OT_LAYOUT_NO_VARIATIONS_INDEX */
if (x_varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX &&
y_varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
return_trace (c->serializer->check_assign (out->format, 1, HB_SERIALIZE_ERROR_INT_OVERFLOW));
if (!c->serializer->embed (xDeviceTable)) return_trace (false);
if (!c->serializer->embed (yDeviceTable)) return_trace (false);
out->xDeviceTable.serialize_copy (c->serializer, xDeviceTable, this, 0, hb_serialize_context_t::Head, &c->plan->layout_variation_idx_delta_map);
out->yDeviceTable.serialize_copy (c->serializer, yDeviceTable, this, 0, hb_serialize_context_t::Head, &c->plan->layout_variation_idx_delta_map);
return_trace (out);
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
(this+xDeviceTable).collect_variation_indices (c);
(this+yDeviceTable).collect_variation_indices (c);
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
@@ -0,0 +1,87 @@
#ifndef OT_LAYOUT_GPOS_ANCHORMATRIX_HH
#define OT_LAYOUT_GPOS_ANCHORMATRIX_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct AnchorMatrix
{
HBUINT16 rows; /* Number of rows */
UnsizedArrayOf<Offset16To<Anchor, AnchorMatrix>>
matrixZ; /* Matrix of offsets to Anchor tables--
* from beginning of AnchorMatrix table */
public:
DEFINE_SIZE_ARRAY (2, matrixZ);
bool sanitize (hb_sanitize_context_t *c, unsigned int cols) const
{
TRACE_SANITIZE (this);
if (!c->check_struct (this)) return_trace (false);
hb_barrier ();
if (unlikely (hb_unsigned_mul_overflows (rows, cols))) return_trace (false);
unsigned int count = rows * cols;
if (!c->check_array (matrixZ.arrayZ, count)) return_trace (false);
if (c->lazy_some_gpos)
return_trace (true);
hb_barrier ();
for (unsigned int i = 0; i < count; i++)
if (!matrixZ[i].sanitize (c, this)) return_trace (false);
return_trace (true);
}
const Anchor& get_anchor (hb_ot_apply_context_t *c,
unsigned int row, unsigned int col,
unsigned int cols, bool *found) const
{
*found = false;
if (unlikely (row >= rows || col >= cols)) return Null (Anchor);
auto &offset = matrixZ[row * cols + col];
if (unlikely (!offset.sanitize (&c->sanitizer, this))) return Null (Anchor);
hb_barrier ();
*found = !offset.is_null ();
return this+offset;
}
template <typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
Iterator index_iter) const
{
for (unsigned i : index_iter)
(this+matrixZ[i]).collect_variation_indices (c);
}
template <typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
bool subset (hb_subset_context_t *c,
unsigned num_rows,
Iterator index_iter) const
{
TRACE_SUBSET (this);
auto *out = c->serializer->start_embed (this);
if (!index_iter) return_trace (false);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->rows = num_rows;
for (const unsigned i : index_iter)
{
auto *offset = c->serializer->embed (matrixZ[i]);
if (!offset) return_trace (false);
offset->serialize_subset (c, matrixZ[i], this);
}
return_trace (true);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_ANCHORMATRIX_HH */
@@ -0,0 +1,14 @@
#ifndef OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH
#define OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct ChainContextPos : ChainContext {};
}
}
}
#endif /* OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH */
@@ -0,0 +1,33 @@
#ifndef OT_LAYOUT_GPOS_COMMON_HH
#define OT_LAYOUT_GPOS_COMMON_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
enum attach_type_t {
ATTACH_TYPE_NONE = 0X00,
/* Each attachment should be either a mark or a cursive; can't be both. */
ATTACH_TYPE_MARK = 0X01,
ATTACH_TYPE_CURSIVE = 0X02,
};
/* buffer **position** var allocations */
#define attach_chain() var.i16[0] /* glyph to which this attaches to, relative to current glyphs; negative for going back, positive for forward. */
#define attach_type() var.u8[2] /* attachment type */
/* Note! if attach_chain() is zero, the value of attach_type() is irrelevant. */
template<typename Iterator, typename SrcLookup>
static void SinglePos_serialize (hb_serialize_context_t *c,
const SrcLookup *src,
Iterator it,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
unsigned new_format);
}
}
}
#endif // OT_LAYOUT_GPOS_COMMON_HH
@@ -0,0 +1,14 @@
#ifndef OT_LAYOUT_GPOS_CONTEXTPOS_HH
#define OT_LAYOUT_GPOS_CONTEXTPOS_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct ContextPos : Context {};
}
}
}
#endif /* OT_LAYOUT_GPOS_CONTEXTPOS_HH */
@@ -0,0 +1,35 @@
#ifndef OT_LAYOUT_GPOS_CURSIVEPOS_HH
#define OT_LAYOUT_GPOS_CURSIVEPOS_HH
#include "CursivePosFormat1.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct CursivePos
{
protected:
union {
HBUINT16 format; /* Format identifier */
CursivePosFormat1 format1;
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_CURSIVEPOS_HH */
@@ -0,0 +1,311 @@
#ifndef OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
#include "Anchor.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct EntryExitRecord
{
friend struct CursivePosFormat1;
bool sanitize (hb_sanitize_context_t *c, const struct CursivePosFormat1 *base) const
{
TRACE_SANITIZE (this);
return_trace (entryAnchor.sanitize (c, base) && exitAnchor.sanitize (c, base));
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const struct CursivePosFormat1 *src_base) const
{
(src_base+entryAnchor).collect_variation_indices (c);
(src_base+exitAnchor).collect_variation_indices (c);
}
bool subset (hb_subset_context_t *c,
const struct CursivePosFormat1 *src_base) const
{
TRACE_SERIALIZE (this);
auto *out = c->serializer->embed (this);
if (unlikely (!out)) return_trace (false);
bool ret = false;
ret |= out->entryAnchor.serialize_subset (c, entryAnchor, src_base);
ret |= out->exitAnchor.serialize_subset (c, exitAnchor, src_base);
return_trace (ret);
}
protected:
Offset16To<Anchor, struct CursivePosFormat1>
entryAnchor; /* Offset to EntryAnchor table--from
* beginning of CursivePos
* subtable--may be NULL */
Offset16To<Anchor, struct CursivePosFormat1>
exitAnchor; /* Offset to ExitAnchor table--from
* beginning of CursivePos
* subtable--may be NULL */
public:
DEFINE_SIZE_STATIC (4);
};
static void
reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent) {
int chain = pos[i].attach_chain(), type = pos[i].attach_type();
if (likely (!chain || 0 == (type & ATTACH_TYPE_CURSIVE)))
return;
pos[i].attach_chain() = 0;
unsigned int j = (int) i + chain;
/* Stop if we see new parent in the chain. */
if (j == new_parent)
return;
reverse_cursive_minor_offset (pos, j, direction, new_parent);
if (HB_DIRECTION_IS_HORIZONTAL (direction))
pos[j].y_offset = -pos[i].y_offset;
else
pos[j].x_offset = -pos[i].x_offset;
pos[j].attach_chain() = -chain;
pos[j].attach_type() = type;
}
struct CursivePosFormat1
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
Offset16To<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of subtable */
Array16Of<EntryExitRecord>
entryExitRecord; /* Array of EntryExit records--in
* Coverage Index order */
public:
DEFINE_SIZE_ARRAY (6, entryExitRecord);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (unlikely (!coverage.sanitize (c, this)))
return_trace (false);
if (c->lazy_some_gpos)
return_trace (entryExitRecord.sanitize_shallow (c));
else
return_trace (entryExitRecord.sanitize (c, this));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
+ hb_zip (this+coverage, entryExitRecord)
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
| hb_apply ([&] (const EntryExitRecord& record) { record.collect_variation_indices (c, this); })
;
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
const Coverage &get_coverage () const { return this+coverage; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
const EntryExitRecord &this_record = entryExitRecord[(this+coverage).get_coverage (buffer->cur().codepoint)];
if (!this_record.entryAnchor ||
unlikely (!this_record.entryAnchor.sanitize (&c->sanitizer, this))) return_trace (false);
hb_barrier ();
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset_fast (buffer->idx);
unsigned unsafe_from;
if (unlikely (!skippy_iter.prev (&unsafe_from)))
{
buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1);
return_trace (false);
}
const EntryExitRecord &prev_record = entryExitRecord[(this+coverage).get_coverage (buffer->info[skippy_iter.idx].codepoint)];
if (!prev_record.exitAnchor ||
unlikely (!prev_record.exitAnchor.sanitize (&c->sanitizer, this)))
{
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
return_trace (false);
}
hb_barrier ();
unsigned int i = skippy_iter.idx;
unsigned int j = buffer->idx;
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"cursive attaching glyph at %u to glyph at %u",
i, j);
}
buffer->unsafe_to_break (i, j + 1);
float entry_x, entry_y, exit_x, exit_y;
(this+prev_record.exitAnchor).get_anchor (c, buffer->info[i].codepoint, &exit_x, &exit_y);
(this+this_record.entryAnchor).get_anchor (c, buffer->info[j].codepoint, &entry_x, &entry_y);
hb_glyph_position_t *pos = buffer->pos;
hb_position_t d;
/* Main-direction adjustment */
switch (c->direction) {
case HB_DIRECTION_LTR:
pos[i].x_advance = roundf (exit_x) + pos[i].x_offset;
d = roundf (entry_x) + pos[j].x_offset;
pos[j].x_advance -= d;
pos[j].x_offset -= d;
break;
case HB_DIRECTION_RTL:
d = roundf (exit_x) + pos[i].x_offset;
pos[i].x_advance -= d;
pos[i].x_offset -= d;
pos[j].x_advance = roundf (entry_x) + pos[j].x_offset;
break;
case HB_DIRECTION_TTB:
pos[i].y_advance = roundf (exit_y) + pos[i].y_offset;
d = roundf (entry_y) + pos[j].y_offset;
pos[j].y_advance -= d;
pos[j].y_offset -= d;
break;
case HB_DIRECTION_BTT:
d = roundf (exit_y) + pos[i].y_offset;
pos[i].y_advance -= d;
pos[i].y_offset -= d;
pos[j].y_advance = roundf (entry_y);
break;
case HB_DIRECTION_INVALID:
default:
break;
}
/* Cross-direction adjustment */
/* We attach child to parent (think graph theory and rooted trees whereas
* the root stays on baseline and each node aligns itself against its
* parent.
*
* Optimize things for the case of RightToLeft, as that's most common in
* Arabic. */
unsigned int child = i;
unsigned int parent = j;
hb_position_t x_offset = roundf (entry_x - exit_x);
hb_position_t y_offset = roundf (entry_y - exit_y);
if (!(c->lookup_props & LookupFlag::RightToLeft))
{
unsigned int k = child;
child = parent;
parent = k;
x_offset = -x_offset;
y_offset = -y_offset;
}
/* If child was already connected to someone else, walk through its old
* chain and reverse the link direction, such that the whole tree of its
* previous connection now attaches to new parent. Watch out for case
* where new parent is on the path from old chain...
*/
reverse_cursive_minor_offset (pos, child, c->direction, parent);
pos[child].attach_type() = ATTACH_TYPE_CURSIVE;
pos[child].attach_chain() = (int) parent - (int) child;
buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
pos[child].y_offset = y_offset;
else
pos[child].x_offset = x_offset;
/* If parent was attached to child, separate them.
* https://github.com/harfbuzz/harfbuzz/issues/2469
*/
if (unlikely (pos[parent].attach_chain() == -pos[child].attach_chain()))
{
pos[parent].attach_chain() = 0;
if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
pos[parent].y_offset = 0;
else
pos[parent].x_offset = 0;
}
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"cursive attached glyph at %u to glyph at %u",
i, j);
}
buffer->idx++;
return_trace (true);
}
template <typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_subset_context_t *c,
Iterator it,
const struct CursivePosFormat1 *src_base)
{
if (unlikely (!c->serializer->extend_min ((*this)))) return;
this->format = 1;
this->entryExitRecord.len = it.len ();
for (const EntryExitRecord& entry_record : + it
| hb_map (hb_second))
entry_record.subset (c, src_base);
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
coverage.serialize_serialize (c->serializer, glyphs);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
auto it =
+ hb_zip (this+coverage, entryExitRecord)
| hb_filter (glyphset, hb_first)
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const EntryExitRecord&> p) -> hb_pair_t<hb_codepoint_t, const EntryExitRecord&>
{ return hb_pair (glyph_map[p.first], p.second);})
;
bool ret = bool (it);
out->serialize (c, it, this);
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH */
@@ -0,0 +1,17 @@
#ifndef OT_LAYOUT_GPOS_EXTENSIONPOS_HH
#define OT_LAYOUT_GPOS_EXTENSIONPOS_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct ExtensionPos : Extension<ExtensionPos>
{
typedef struct PosLookupSubTable SubTable;
};
}
}
}
#endif /* OT_LAYOUT_GPOS_EXTENSIONPOS_HH */
@@ -0,0 +1,171 @@
#ifndef OT_LAYOUT_GPOS_GPOS_HH
#define OT_LAYOUT_GPOS_GPOS_HH
#include "../../../hb-ot-layout-common.hh"
#include "../../../hb-ot-layout-gsubgpos.hh"
#include "Common.hh"
#include "PosLookup.hh"
namespace OT {
using Layout::GPOS_impl::PosLookup;
namespace Layout {
static void
propagate_attachment_offsets (hb_glyph_position_t *pos,
unsigned int len,
unsigned int i,
hb_direction_t direction,
unsigned nesting_level = HB_MAX_NESTING_LEVEL);
/*
* GPOS -- Glyph Positioning
* https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
*/
struct GPOS : GSUBGPOS
{
static constexpr hb_tag_t tableTag = HB_OT_TAG_GPOS;
using Lookup = PosLookup;
const PosLookup& get_lookup (unsigned int i) const
{ return static_cast<const PosLookup &> (GSUBGPOS::get_lookup (i)); }
static inline void position_start (hb_font_t *font, hb_buffer_t *buffer);
static inline void position_finish_advances (hb_font_t *font, hb_buffer_t *buffer);
static inline void position_finish_offsets (hb_font_t *font, hb_buffer_t *buffer);
bool subset (hb_subset_context_t *c) const
{
hb_subset_layout_context_t l (c, tableTag);
return GSUBGPOS::subset<PosLookup> (&l);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (GSUBGPOS::sanitize<PosLookup> (c));
}
HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
hb_face_t *face) const;
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
for (unsigned i = 0; i < GSUBGPOS::get_lookup_count (); i++)
{
if (!c->gpos_lookups->has (i)) continue;
const PosLookup &l = get_lookup (i);
l.dispatch (c);
}
}
void closure_lookups (hb_face_t *face,
const hb_set_t *glyphs,
hb_set_t *lookup_indexes /* IN/OUT */) const
{ GSUBGPOS::closure_lookups<PosLookup> (face, glyphs, lookup_indexes); }
typedef GSUBGPOS::accelerator_t<GPOS> accelerator_t;
};
static void
propagate_attachment_offsets (hb_glyph_position_t *pos,
unsigned int len,
unsigned int i,
hb_direction_t direction,
unsigned nesting_level)
{
/* Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
* offset of glyph they are attached to. */
int chain = pos[i].attach_chain(), type = pos[i].attach_type();
if (likely (!chain))
return;
pos[i].attach_chain() = 0;
unsigned int j = (int) i + chain;
if (unlikely (j >= len))
return;
if (unlikely (!nesting_level))
return;
propagate_attachment_offsets (pos, len, j, direction, nesting_level - 1);
assert (!!(type & GPOS_impl::ATTACH_TYPE_MARK) ^ !!(type & GPOS_impl::ATTACH_TYPE_CURSIVE));
if (type & GPOS_impl::ATTACH_TYPE_CURSIVE)
{
if (HB_DIRECTION_IS_HORIZONTAL (direction))
pos[i].y_offset += pos[j].y_offset;
else
pos[i].x_offset += pos[j].x_offset;
}
else /*if (type & GPOS_impl::ATTACH_TYPE_MARK)*/
{
pos[i].x_offset += pos[j].x_offset;
pos[i].y_offset += pos[j].y_offset;
assert (j < i);
if (HB_DIRECTION_IS_FORWARD (direction))
for (unsigned int k = j; k < i; k++) {
pos[i].x_offset -= pos[k].x_advance;
pos[i].y_offset -= pos[k].y_advance;
}
else
for (unsigned int k = j + 1; k < i + 1; k++) {
pos[i].x_offset += pos[k].x_advance;
pos[i].y_offset += pos[k].y_advance;
}
}
}
void
GPOS::position_start (hb_font_t *font HB_UNUSED, hb_buffer_t *buffer)
{
unsigned int count = buffer->len;
for (unsigned int i = 0; i < count; i++)
buffer->pos[i].attach_chain() = buffer->pos[i].attach_type() = 0;
}
void
GPOS::position_finish_advances (hb_font_t *font HB_UNUSED, hb_buffer_t *buffer HB_UNUSED)
{
//_hb_buffer_assert_gsubgpos_vars (buffer);
}
void
GPOS::position_finish_offsets (hb_font_t *font, hb_buffer_t *buffer)
{
_hb_buffer_assert_gsubgpos_vars (buffer);
unsigned int len;
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, &len);
hb_direction_t direction = buffer->props.direction;
/* Handle attachments */
if (buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT)
for (unsigned i = 0; i < len; i++)
propagate_attachment_offsets (pos, len, i, direction);
if (unlikely (font->slant))
{
for (unsigned i = 0; i < len; i++)
if (unlikely (pos[i].y_offset))
pos[i].x_offset += roundf (font->slant_xy * pos[i].y_offset);
}
}
}
struct GPOS_accelerator_t : Layout::GPOS::accelerator_t {
GPOS_accelerator_t (hb_face_t *face) : Layout::GPOS::accelerator_t (face) {}
};
}
#endif /* OT_LAYOUT_GPOS_GPOS_HH */
@@ -0,0 +1,57 @@
#ifndef OT_LAYOUT_GPOS_LIGATUREARRAY_HH
#define OT_LAYOUT_GPOS_LIGATUREARRAY_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
typedef AnchorMatrix LigatureAttach; /* component-major--
* in order of writing direction--,
* mark-minor--
* ordered by class--zero-based. */
/* Array of LigatureAttach tables ordered by LigatureCoverage Index */
struct LigatureArray : List16OfOffset16To<LigatureAttach>
{
template <typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
bool subset (hb_subset_context_t *c,
Iterator coverage,
unsigned class_count,
const hb_map_t *klass_mapping) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
auto *out = c->serializer->start_embed (this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
bool ret = false;
for (const auto _ : + hb_zip (coverage, *this)
| hb_filter (glyphset, hb_first))
{
auto *matrix = out->serialize_append (c->serializer);
if (unlikely (!matrix)) return_trace (false);
const LigatureAttach& src = (this + _.second);
auto indexes =
+ hb_range (src.rows * class_count)
| hb_filter ([=] (unsigned index) { return klass_mapping->has (index % class_count); })
;
ret |= matrix->serialize_subset (c,
_.second,
this,
src.rows,
indexes);
}
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_LIGATUREARRAY_HH */
@@ -0,0 +1,128 @@
#ifndef OT_LAYOUT_GPOS_MARKARRAY_HH
#define OT_LAYOUT_GPOS_MARKARRAY_HH
#include "AnchorMatrix.hh"
#include "MarkRecord.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct MarkArray : Array16Of<MarkRecord> /* Array of MarkRecords--in Coverage order */
{
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (Array16Of<MarkRecord>::sanitize (c, this));
}
bool apply (hb_ot_apply_context_t *c,
unsigned int mark_index, unsigned int glyph_index,
const AnchorMatrix &anchors, unsigned int class_count,
unsigned int glyph_pos) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
const MarkRecord &record = Array16Of<MarkRecord>::operator[](mark_index);
unsigned int mark_class = record.klass;
const Anchor& mark_anchor = this + record.markAnchor;
bool found;
const Anchor& glyph_anchor = anchors.get_anchor (c, glyph_index, mark_class, class_count, &found);
/* If this subtable doesn't have an anchor for this base and this class,
* return false such that the subsequent subtables have a chance at it. */
if (unlikely (!found)) return_trace (false);
float mark_x, mark_y, base_x, base_y;
buffer->unsafe_to_break (glyph_pos, buffer->idx + 1);
mark_anchor.get_anchor (c, buffer->cur().codepoint, &mark_x, &mark_y);
glyph_anchor.get_anchor (c, buffer->info[glyph_pos].codepoint, &base_x, &base_y);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"attaching mark glyph at %u to glyph at %u",
c->buffer->idx, glyph_pos);
}
hb_glyph_position_t &o = buffer->cur_pos();
o.x_offset = roundf (base_x - mark_x);
o.y_offset = roundf (base_y - mark_y);
o.attach_type() = ATTACH_TYPE_MARK;
o.attach_chain() = (int) glyph_pos - (int) buffer->idx;
buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"attached mark glyph at %u to glyph at %u",
c->buffer->idx, glyph_pos);
}
buffer->idx++;
return_trace (true);
}
template <typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
bool subset (hb_subset_context_t *c,
Iterator coverage,
const hb_map_t *klass_mapping) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
auto* out = c->serializer->start_embed (this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
auto mark_iter =
+ hb_zip (coverage, this->iter ())
| hb_filter (glyphset, hb_first)
| hb_map (hb_second)
;
bool ret = false;
unsigned new_length = 0;
for (const auto& mark_record : mark_iter) {
ret |= mark_record.subset (c, this, klass_mapping);
new_length++;
}
if (unlikely (!c->serializer->check_assign (out->len, new_length,
HB_SERIALIZE_ERROR_ARRAY_OVERFLOW)))
return_trace (false);
return_trace (ret);
}
};
HB_INTERNAL inline
void Markclass_closure_and_remap_indexes (const Coverage &mark_coverage,
const MarkArray &mark_array,
const hb_set_t &glyphset,
hb_map_t* klass_mapping /* INOUT */)
{
hb_set_t orig_classes;
+ hb_zip (mark_coverage, mark_array)
| hb_filter (glyphset, hb_first)
| hb_map (hb_second)
| hb_map (&MarkRecord::get_class)
| hb_sink (orig_classes)
;
unsigned idx = 0;
for (auto klass : orig_classes.iter ())
{
if (klass_mapping->has (klass)) continue;
klass_mapping->set (klass, idx);
idx++;
}
}
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKARRAY_HH */
@@ -0,0 +1,41 @@
#ifndef OT_LAYOUT_GPOS_MARKBASEPOS_HH
#define OT_LAYOUT_GPOS_MARKBASEPOS_HH
#include "MarkBasePosFormat1.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct MarkBasePos
{
protected:
union {
HBUINT16 format; /* Format identifier */
MarkBasePosFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
MarkBasePosFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKBASEPOS_HH */
@@ -0,0 +1,243 @@
#ifndef OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH
#include "MarkArray.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
typedef AnchorMatrix BaseArray; /* base-major--
* in order of BaseCoverage Index--,
* mark-minor--
* ordered by class--zero-based. */
template <typename Types>
struct MarkBasePosFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
markCoverage; /* Offset to MarkCoverage table--from
* beginning of MarkBasePos subtable */
typename Types::template OffsetTo<Coverage>
baseCoverage; /* Offset to BaseCoverage table--from
* beginning of MarkBasePos subtable */
HBUINT16 classCount; /* Number of classes defined for marks */
typename Types::template OffsetTo<MarkArray>
markArray; /* Offset to MarkArray table--from
* beginning of MarkBasePos subtable */
typename Types::template OffsetTo<BaseArray>
baseArray; /* Offset to BaseArray table--from
* beginning of MarkBasePos subtable */
public:
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
markCoverage.sanitize (c, this) &&
baseCoverage.sanitize (c, this) &&
markArray.sanitize (c, this) &&
baseArray.sanitize (c, this, (unsigned int) classCount));
}
bool intersects (const hb_set_t *glyphs) const
{
return (this+markCoverage).intersects (glyphs) &&
(this+baseCoverage).intersects (glyphs);
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
+ hb_zip (this+markCoverage, this+markArray)
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+markArray)); })
;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, *c->glyph_set, &klass_mapping);
unsigned basecount = (this+baseArray).rows;
auto base_iter =
+ hb_zip (this+baseCoverage, hb_range (basecount))
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
;
hb_sorted_vector_t<unsigned> base_indexes;
for (const unsigned row : base_iter)
{
+ hb_range ((unsigned) classCount)
| hb_filter (klass_mapping)
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
| hb_sink (base_indexes)
;
}
(this+baseArray).collect_variation_indices (c, base_indexes.iter ());
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+markCoverage).collect_coverage (c->input))) return;
if (unlikely (!(this+baseCoverage).collect_coverage (c->input))) return;
}
const Coverage &get_coverage () const { return this+markCoverage; }
static inline bool accept (hb_buffer_t *buffer, unsigned idx)
{
/* We only want to attach to the first of a MultipleSubst sequence.
* https://github.com/harfbuzz/harfbuzz/issues/740
* Reject others...
* ...but stop if we find a mark in the MultipleSubst sequence:
* https://github.com/harfbuzz/harfbuzz/issues/1020 */
return !_hb_glyph_info_multiplied (&buffer->info[idx]) ||
0 == _hb_glyph_info_get_lig_comp (&buffer->info[idx]) ||
(idx == 0 ||
_hb_glyph_info_is_mark (&buffer->info[idx - 1]) ||
!_hb_glyph_info_multiplied (&buffer->info[idx - 1]) ||
_hb_glyph_info_get_lig_id (&buffer->info[idx]) !=
_hb_glyph_info_get_lig_id (&buffer->info[idx - 1]) ||
_hb_glyph_info_get_lig_comp (&buffer->info[idx]) !=
_hb_glyph_info_get_lig_comp (&buffer->info[idx - 1]) + 1
);
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int mark_index = (this+markCoverage).get_coverage (buffer->cur().codepoint);
if (likely (mark_index == NOT_COVERED)) return_trace (false);
/* Now we search backwards for a non-mark glyph.
* We don't use skippy_iter.prev() to avoid O(n^2) behavior. */
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
if (c->last_base_until > buffer->idx)
{
c->last_base_until = 0;
c->last_base = -1;
}
unsigned j;
for (j = buffer->idx; j > c->last_base_until; j--)
{
auto match = skippy_iter.match (buffer->info[j - 1]);
if (match == skippy_iter.MATCH)
{
// https://github.com/harfbuzz/harfbuzz/issues/4124
if (!accept (buffer, j - 1) &&
NOT_COVERED == (this+baseCoverage).get_coverage (buffer->info[j - 1].codepoint))
match = skippy_iter.SKIP;
}
if (match == skippy_iter.MATCH)
{
c->last_base = (signed) j - 1;
break;
}
}
c->last_base_until = buffer->idx;
if (c->last_base == -1)
{
buffer->unsafe_to_concat_from_outbuffer (0, buffer->idx + 1);
return_trace (false);
}
unsigned idx = (unsigned) c->last_base;
/* Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled */
//if (!_hb_glyph_info_is_base_glyph (&buffer->info[idx])) { return_trace (false); }
unsigned int base_index = (this+baseCoverage).get_coverage (buffer->info[idx].codepoint);
if (base_index == NOT_COVERED)
{
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
return_trace (false);
}
return_trace ((this+markArray).apply (c, mark_index, base_index, this+baseArray, classCount, idx));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, glyphset, &klass_mapping);
if (!klass_mapping.get_population ()) return_trace (false);
out->classCount = klass_mapping.get_population ();
auto mark_iter =
+ hb_zip (this+markCoverage, this+markArray)
| hb_filter (glyphset, hb_first)
;
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
+ mark_iter
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
if (!out->markCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
return_trace (false);
if (unlikely (!out->markArray.serialize_subset (c, markArray, this,
(this+markCoverage).iter (),
&klass_mapping)))
return_trace (false);
unsigned basecount = (this+baseArray).rows;
auto base_iter =
+ hb_zip (this+baseCoverage, hb_range (basecount))
| hb_filter (glyphset, hb_first)
;
new_coverage.reset ();
+ base_iter
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
if (!out->baseCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
return_trace (false);
hb_sorted_vector_t<unsigned> base_indexes;
for (const unsigned row : + base_iter
| hb_map (hb_second))
{
+ hb_range ((unsigned) classCount)
| hb_filter (klass_mapping)
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
| hb_sink (base_indexes)
;
}
return_trace (out->baseArray.serialize_subset (c, baseArray, this,
base_iter.len (),
base_indexes.iter ()));
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH */
@@ -0,0 +1,41 @@
#ifndef OT_LAYOUT_GPOS_MARKLIGPOS_HH
#define OT_LAYOUT_GPOS_MARKLIGPOS_HH
#include "MarkLigPosFormat1.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct MarkLigPos
{
protected:
union {
HBUINT16 format; /* Format identifier */
MarkLigPosFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
MarkLigPosFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKLIGPOS_HH */
@@ -0,0 +1,224 @@
#ifndef OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH
#include "LigatureArray.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
template <typename Types>
struct MarkLigPosFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
markCoverage; /* Offset to Mark Coverage table--from
* beginning of MarkLigPos subtable */
typename Types::template OffsetTo<Coverage>
ligatureCoverage; /* Offset to Ligature Coverage
* table--from beginning of MarkLigPos
* subtable */
HBUINT16 classCount; /* Number of defined mark classes */
typename Types::template OffsetTo<MarkArray>
markArray; /* Offset to MarkArray table--from
* beginning of MarkLigPos subtable */
typename Types::template OffsetTo<LigatureArray>
ligatureArray; /* Offset to LigatureArray table--from
* beginning of MarkLigPos subtable */
public:
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
markCoverage.sanitize (c, this) &&
ligatureCoverage.sanitize (c, this) &&
markArray.sanitize (c, this) &&
ligatureArray.sanitize (c, this, (unsigned int) classCount));
}
bool intersects (const hb_set_t *glyphs) const
{
return (this+markCoverage).intersects (glyphs) &&
(this+ligatureCoverage).intersects (glyphs);
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
+ hb_zip (this+markCoverage, this+markArray)
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+markArray)); })
;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, *c->glyph_set, &klass_mapping);
unsigned ligcount = (this+ligatureArray).len;
auto lig_iter =
+ hb_zip (this+ligatureCoverage, hb_range (ligcount))
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
;
const LigatureArray& lig_array = this+ligatureArray;
for (const unsigned i : lig_iter)
{
hb_sorted_vector_t<unsigned> lig_indexes;
unsigned row_count = lig_array[i].rows;
for (unsigned row : + hb_range (row_count))
{
+ hb_range ((unsigned) classCount)
| hb_filter (klass_mapping)
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
| hb_sink (lig_indexes)
;
}
lig_array[i].collect_variation_indices (c, lig_indexes.iter ());
}
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+markCoverage).collect_coverage (c->input))) return;
if (unlikely (!(this+ligatureCoverage).collect_coverage (c->input))) return;
}
const Coverage &get_coverage () const { return this+markCoverage; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int mark_index = (this+markCoverage).get_coverage (buffer->cur().codepoint);
if (likely (mark_index == NOT_COVERED)) return_trace (false);
/* Now we search backwards for a non-mark glyph */
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
if (c->last_base_until > buffer->idx)
{
c->last_base_until = 0;
c->last_base = -1;
}
unsigned j;
for (j = buffer->idx; j > c->last_base_until; j--)
{
auto match = skippy_iter.match (buffer->info[j - 1]);
if (match == skippy_iter.MATCH)
{
c->last_base = (signed) j - 1;
break;
}
}
c->last_base_until = buffer->idx;
if (c->last_base == -1)
{
buffer->unsafe_to_concat_from_outbuffer (0, buffer->idx + 1);
return_trace (false);
}
unsigned idx = (unsigned) c->last_base;
/* Checking that matched glyph is actually a ligature by GDEF is too strong; disabled */
//if (!_hb_glyph_info_is_ligature (&buffer->info[idx])) { return_trace (false); }
unsigned int lig_index = (this+ligatureCoverage).get_coverage (buffer->info[idx].codepoint);
if (lig_index == NOT_COVERED)
{
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
return_trace (false);
}
const LigatureArray& lig_array = this+ligatureArray;
const LigatureAttach& lig_attach = lig_array[lig_index];
/* Find component to attach to */
unsigned int comp_count = lig_attach.rows;
if (unlikely (!comp_count))
{
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
return_trace (false);
}
/* We must now check whether the ligature ID of the current mark glyph
* is identical to the ligature ID of the found ligature. If yes, we
* can directly use the component index. If not, we attach the mark
* glyph to the last component of the ligature. */
unsigned int comp_index;
unsigned int lig_id = _hb_glyph_info_get_lig_id (&buffer->info[idx]);
unsigned int mark_id = _hb_glyph_info_get_lig_id (&buffer->cur());
unsigned int mark_comp = _hb_glyph_info_get_lig_comp (&buffer->cur());
if (lig_id && lig_id == mark_id && mark_comp > 0)
comp_index = hb_min (comp_count, _hb_glyph_info_get_lig_comp (&buffer->cur())) - 1;
else
comp_index = comp_count - 1;
return_trace ((this+markArray).apply (c, mark_index, comp_index, lig_attach, classCount, idx));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = c->plan->glyph_map_gsub;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, glyphset, &klass_mapping);
if (!klass_mapping.get_population ()) return_trace (false);
out->classCount = klass_mapping.get_population ();
auto mark_iter =
+ hb_zip (this+markCoverage, this+markArray)
| hb_filter (glyphset, hb_first)
;
auto new_mark_coverage =
+ mark_iter
| hb_map_retains_sorting (hb_first)
| hb_map_retains_sorting (glyph_map)
;
if (!out->markCoverage.serialize_serialize (c->serializer, new_mark_coverage))
return_trace (false);
if (unlikely (!out->markArray.serialize_subset (c, markArray, this,
(this+markCoverage).iter (),
&klass_mapping)))
return_trace (false);
auto new_ligature_coverage =
+ hb_iter (this + ligatureCoverage)
| hb_take ((this + ligatureArray).len)
| hb_map_retains_sorting (glyph_map)
| hb_filter ([] (hb_codepoint_t glyph) { return glyph != HB_MAP_VALUE_INVALID; })
;
if (!out->ligatureCoverage.serialize_serialize (c->serializer, new_ligature_coverage))
return_trace (false);
return_trace (out->ligatureArray.serialize_subset (c, ligatureArray, this,
hb_iter (this+ligatureCoverage),
classCount, &klass_mapping));
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH */
@@ -0,0 +1,42 @@
#ifndef OT_LAYOUT_GPOS_MARKMARKPOS_HH
#define OT_LAYOUT_GPOS_MARKMARKPOS_HH
#include "MarkMarkPosFormat1.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct MarkMarkPos
{
protected:
union {
HBUINT16 format; /* Format identifier */
MarkMarkPosFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
MarkMarkPosFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKMARKPOS_HH */
@@ -0,0 +1,231 @@
#ifndef OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH
#include "MarkMarkPosFormat1.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
typedef AnchorMatrix Mark2Array; /* mark2-major--
* in order of Mark2Coverage Index--,
* mark1-minor--
* ordered by class--zero-based. */
template <typename Types>
struct MarkMarkPosFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
mark1Coverage; /* Offset to Combining Mark1 Coverage
* table--from beginning of MarkMarkPos
* subtable */
typename Types::template OffsetTo<Coverage>
mark2Coverage; /* Offset to Combining Mark2 Coverage
* table--from beginning of MarkMarkPos
* subtable */
HBUINT16 classCount; /* Number of defined mark classes */
typename Types::template OffsetTo<MarkArray>
mark1Array; /* Offset to Mark1Array table--from
* beginning of MarkMarkPos subtable */
typename Types::template OffsetTo<Mark2Array>
mark2Array; /* Offset to Mark2Array table--from
* beginning of MarkMarkPos subtable */
public:
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
mark1Coverage.sanitize (c, this) &&
mark2Coverage.sanitize (c, this) &&
mark1Array.sanitize (c, this) &&
hb_barrier () &&
mark2Array.sanitize (c, this, (unsigned int) classCount));
}
bool intersects (const hb_set_t *glyphs) const
{
return (this+mark1Coverage).intersects (glyphs) &&
(this+mark2Coverage).intersects (glyphs);
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
+ hb_zip (this+mark1Coverage, this+mark1Array)
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+mark1Array)); })
;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+mark1Coverage, this+mark1Array, *c->glyph_set, &klass_mapping);
unsigned mark2_count = (this+mark2Array).rows;
auto mark2_iter =
+ hb_zip (this+mark2Coverage, hb_range (mark2_count))
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
;
hb_sorted_vector_t<unsigned> mark2_indexes;
for (const unsigned row : mark2_iter)
{
+ hb_range ((unsigned) classCount)
| hb_filter (klass_mapping)
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
| hb_sink (mark2_indexes)
;
}
(this+mark2Array).collect_variation_indices (c, mark2_indexes.iter ());
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+mark1Coverage).collect_coverage (c->input))) return;
if (unlikely (!(this+mark2Coverage).collect_coverage (c->input))) return;
}
const Coverage &get_coverage () const { return this+mark1Coverage; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int mark1_index = (this+mark1Coverage).get_coverage (buffer->cur().codepoint);
if (likely (mark1_index == NOT_COVERED)) return_trace (false);
/* now we search backwards for a suitable mark glyph until a non-mark glyph */
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset_fast (buffer->idx);
skippy_iter.set_lookup_props (c->lookup_props & ~(uint32_t)LookupFlag::IgnoreFlags);
unsigned unsafe_from;
if (unlikely (!skippy_iter.prev (&unsafe_from)))
{
buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1);
return_trace (false);
}
if (likely (!_hb_glyph_info_is_mark (&buffer->info[skippy_iter.idx])))
{
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
return_trace (false);
}
unsigned int j = skippy_iter.idx;
unsigned int id1 = _hb_glyph_info_get_lig_id (&buffer->cur());
unsigned int id2 = _hb_glyph_info_get_lig_id (&buffer->info[j]);
unsigned int comp1 = _hb_glyph_info_get_lig_comp (&buffer->cur());
unsigned int comp2 = _hb_glyph_info_get_lig_comp (&buffer->info[j]);
if (likely (id1 == id2))
{
if (id1 == 0) /* Marks belonging to the same base. */
goto good;
else if (comp1 == comp2) /* Marks belonging to the same ligature component. */
goto good;
}
else
{
/* If ligature ids don't match, it may be the case that one of the marks
* itself is a ligature. In which case match. */
if ((id1 > 0 && !comp1) || (id2 > 0 && !comp2))
goto good;
}
/* Didn't match. */
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
return_trace (false);
good:
unsigned int mark2_index = (this+mark2Coverage).get_coverage (buffer->info[j].codepoint);
if (mark2_index == NOT_COVERED)
{
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
return_trace (false);
}
return_trace ((this+mark1Array).apply (c, mark1_index, mark2_index, this+mark2Array, classCount, j));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_map_t klass_mapping;
Markclass_closure_and_remap_indexes (this+mark1Coverage, this+mark1Array, glyphset, &klass_mapping);
if (!klass_mapping.get_population ()) return_trace (false);
out->classCount = klass_mapping.get_population ();
auto mark1_iter =
+ hb_zip (this+mark1Coverage, this+mark1Array)
| hb_filter (glyphset, hb_first)
;
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
+ mark1_iter
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
if (!out->mark1Coverage.serialize_serialize (c->serializer, new_coverage.iter ()))
return_trace (false);
if (unlikely (!out->mark1Array.serialize_subset (c, mark1Array, this,
(this+mark1Coverage).iter (),
&klass_mapping)))
return_trace (false);
unsigned mark2count = (this+mark2Array).rows;
auto mark2_iter =
+ hb_zip (this+mark2Coverage, hb_range (mark2count))
| hb_filter (glyphset, hb_first)
;
new_coverage.reset ();
+ mark2_iter
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
if (!out->mark2Coverage.serialize_serialize (c->serializer, new_coverage.iter ()))
return_trace (false);
hb_sorted_vector_t<unsigned> mark2_indexes;
for (const unsigned row : + mark2_iter
| hb_map (hb_second))
{
+ hb_range ((unsigned) classCount)
| hb_filter (klass_mapping)
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
| hb_sink (mark2_indexes)
;
}
return_trace (out->mark2Array.serialize_subset (c, mark2Array, this,
mark2_iter.len (),
mark2_indexes.iter ()));
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH */
@@ -0,0 +1,51 @@
#ifndef OT_LAYOUT_GPOS_MARKRECORD_HH
#define OT_LAYOUT_GPOS_MARKRECORD_HH
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct MarkRecord
{
friend struct MarkArray;
public:
HBUINT16 klass; /* Class defined for this mark */
Offset16To<Anchor>
markAnchor; /* Offset to Anchor table--from
* beginning of MarkArray table */
public:
DEFINE_SIZE_STATIC (4);
unsigned get_class () const { return (unsigned) klass; }
bool sanitize (hb_sanitize_context_t *c, const void *base) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) && markAnchor.sanitize (c, base));
}
bool subset (hb_subset_context_t *c,
const void *src_base,
const hb_map_t *klass_mapping) const
{
TRACE_SUBSET (this);
auto *out = c->serializer->embed (this);
if (unlikely (!out)) return_trace (false);
out->klass = klass_mapping->get (klass);
return_trace (out->markAnchor.serialize_subset (c, markAnchor, src_base));
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const void *src_base) const
{
(src_base+markAnchor).collect_variation_indices (c);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_MARKRECORD_HH */
@@ -0,0 +1,46 @@
#ifndef OT_LAYOUT_GPOS_PAIRPOS_HH
#define OT_LAYOUT_GPOS_PAIRPOS_HH
#include "PairPosFormat1.hh"
#include "PairPosFormat2.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct PairPos
{
protected:
union {
HBUINT16 format; /* Format identifier */
PairPosFormat1_3<SmallTypes> format1;
PairPosFormat2_4<SmallTypes> format2;
#ifndef HB_NO_BEYOND_64K
PairPosFormat1_3<MediumTypes> format3;
PairPosFormat2_4<MediumTypes> format4;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_PAIRPOS_HH
@@ -0,0 +1,233 @@
#ifndef OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
#include "PairSet.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
template <typename Types>
struct PairPosFormat1_3
{
using PairSet = GPOS_impl::PairSet<Types>;
using PairValueRecord = GPOS_impl::PairValueRecord<Types>;
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of subtable */
ValueFormat valueFormat[2]; /* [0] Defines the types of data in
* ValueRecord1--for the first glyph
* in the pair--may be zero (0) */
/* [1] Defines the types of data in
* ValueRecord2--for the second glyph
* in the pair--may be zero (0) */
Array16Of<typename Types::template OffsetTo<PairSet>>
pairSet; /* Array of PairSet tables
* ordered by Coverage Index */
public:
DEFINE_SIZE_ARRAY (8 + Types::size, pairSet);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (!c->check_struct (this)) return_trace (false);
hb_barrier ();
unsigned int len1 = valueFormat[0].get_len ();
unsigned int len2 = valueFormat[1].get_len ();
typename PairSet::sanitize_closure_t closure =
{
valueFormat,
len1,
PairSet::get_size (len1, len2)
};
return_trace (coverage.sanitize (c, this) && pairSet.sanitize (c, this, &closure));
}
bool intersects (const hb_set_t *glyphs) const
{
auto &cov = this+coverage;
if (pairSet.len > glyphs->get_population () * hb_bit_storage ((unsigned) pairSet.len) / 4)
{
for (hb_codepoint_t g : glyphs->iter())
{
unsigned i = cov.get_coverage (g);
if ((this+pairSet[i]).intersects (glyphs, valueFormat))
return true;
}
return false;
}
return
+ hb_zip (cov, pairSet)
| hb_filter (*glyphs, hb_first)
| hb_map (hb_second)
| hb_map ([glyphs, this] (const typename Types::template OffsetTo<PairSet> &_)
{ return (this+_).intersects (glyphs, valueFormat); })
| hb_any
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
if ((!valueFormat[0].has_device ()) && (!valueFormat[1].has_device ())) return;
auto it =
+ hb_zip (this+coverage, pairSet)
| hb_filter (c->glyph_set, hb_first)
| hb_map (hb_second)
;
if (!it) return;
+ it
| hb_map (hb_add (this))
| hb_apply ([&] (const PairSet& _) { _.collect_variation_indices (c, valueFormat); })
;
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
unsigned int count = pairSet.len;
for (unsigned int i = 0; i < count; i++)
(this+pairSet[i]).collect_glyphs (c, valueFormat);
}
const Coverage &get_coverage () const { return this+coverage; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset_fast (buffer->idx);
unsigned unsafe_to;
if (unlikely (!skippy_iter.next (&unsafe_to)))
{
buffer->unsafe_to_concat (buffer->idx, unsafe_to);
return_trace (false);
}
return_trace ((this+pairSet[index]).apply (c, valueFormat, skippy_iter.idx));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_pair_t<unsigned, unsigned> newFormats = hb_pair (valueFormat[0], valueFormat[1]);
if (c->plan->normalized_coords)
{
/* all device flags will be dropped when full instancing, no need to strip
* hints, also do not strip emtpy cause we don't compute the new default
* value during stripping */
newFormats = compute_effective_value_formats (glyphset, false, false, &c->plan->layout_variation_idx_delta_map);
}
/* do not strip hints for VF */
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
{
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
bool has_fvar = (blob != hb_blob_get_empty ());
hb_blob_destroy (blob);
bool strip = !has_fvar;
/* special case: strip hints when a VF has no GDEF varstore after
* subsetting*/
if (has_fvar && !c->plan->has_gdef_varstore)
strip = true;
newFormats = compute_effective_value_formats (glyphset, strip, true);
}
out->valueFormat[0] = newFormats.first;
out->valueFormat[1] = newFormats.second;
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
+ hb_zip (this+coverage, pairSet)
| hb_filter (glyphset, hb_first)
| hb_filter ([this, c, out] (const typename Types::template OffsetTo<PairSet>& _)
{
auto snap = c->serializer->snapshot ();
auto *o = out->pairSet.serialize_append (c->serializer);
if (unlikely (!o)) return false;
bool ret = o->serialize_subset (c, _, this, valueFormat, out->valueFormat);
if (!ret)
{
out->pairSet.pop ();
c->serializer->revert (snap);
}
return ret;
},
hb_second)
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
return_trace (bool (new_coverage));
}
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_set_t& glyphset,
bool strip_hints, bool strip_empty,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map = nullptr) const
{
unsigned record_size = PairSet::get_size (valueFormat);
unsigned format1 = 0;
unsigned format2 = 0;
for (const auto & _ :
+ hb_zip (this+coverage, pairSet)
| hb_filter (glyphset, hb_first)
| hb_map (hb_second)
)
{
const PairSet& set = (this + _);
const PairValueRecord *record = &set.firstPairValueRecord;
unsigned count = set.len;
for (unsigned i = 0; i < count; i++)
{
if (record->intersects (glyphset))
{
format1 = format1 | valueFormat[0].get_effective_format (record->get_values_1 (), strip_hints, strip_empty, &set, varidx_delta_map);
format2 = format2 | valueFormat[1].get_effective_format (record->get_values_2 (valueFormat[0]), strip_hints, strip_empty, &set, varidx_delta_map);
}
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
if (format1 == valueFormat[0] && format2 == valueFormat[1])
break;
}
return hb_pair (format1, format2);
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
@@ -0,0 +1,359 @@
#ifndef OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
#define OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
#include "ValueFormat.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
template <typename Types>
struct PairPosFormat2_4 : ValueBase
{
protected:
HBUINT16 format; /* Format identifier--format = 2 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of subtable */
ValueFormat valueFormat1; /* ValueRecord definition--for the
* first glyph of the pair--may be zero
* (0) */
ValueFormat valueFormat2; /* ValueRecord definition--for the
* second glyph of the pair--may be
* zero (0) */
typename Types::template OffsetTo<ClassDef>
classDef1; /* Offset to ClassDef table--from
* beginning of PairPos subtable--for
* the first glyph of the pair */
typename Types::template OffsetTo<ClassDef>
classDef2; /* Offset to ClassDef table--from
* beginning of PairPos subtable--for
* the second glyph of the pair */
HBUINT16 class1Count; /* Number of classes in ClassDef1
* table--includes Class0 */
HBUINT16 class2Count; /* Number of classes in ClassDef2
* table--includes Class0 */
ValueRecord values; /* Matrix of value pairs:
* class1-major, class2-minor,
* Each entry has value1 and value2 */
public:
DEFINE_SIZE_ARRAY (10 + 3 * Types::size, values);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (!(c->check_struct (this)
&& coverage.sanitize (c, this)
&& classDef1.sanitize (c, this)
&& classDef2.sanitize (c, this))) return_trace (false);
unsigned int len1 = valueFormat1.get_len ();
unsigned int len2 = valueFormat2.get_len ();
unsigned int stride = HBUINT16::static_size * (len1 + len2);
unsigned int count = (unsigned int) class1Count * (unsigned int) class2Count;
return_trace (c->check_range ((const void *) values,
count,
stride) &&
(c->lazy_some_gpos ||
(valueFormat1.sanitize_values_stride_unsafe (c, this, &values[0], count, stride) &&
valueFormat2.sanitize_values_stride_unsafe (c, this, &values[len1], count, stride))));
}
bool intersects (const hb_set_t *glyphs) const
{
return (this+coverage).intersects (glyphs) &&
(this+classDef2).intersects (glyphs);
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
if (!intersects (c->glyph_set)) return;
if ((!valueFormat1.has_device ()) && (!valueFormat2.has_device ())) return;
hb_set_t klass1_glyphs, klass2_glyphs;
if (!(this+classDef1).collect_coverage (&klass1_glyphs)) return;
if (!(this+classDef2).collect_coverage (&klass2_glyphs)) return;
hb_set_t class1_set, class2_set;
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (this + coverage))
{
if (!klass1_glyphs.has (cp)) class1_set.add (0);
else
{
unsigned klass1 = (this+classDef1).get (cp);
class1_set.add (klass1);
}
}
class2_set.add (0);
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (klass2_glyphs))
{
unsigned klass2 = (this+classDef2).get (cp);
class2_set.add (klass2);
}
if (class1_set.is_empty ()
|| class2_set.is_empty ()
|| (class2_set.get_population() == 1 && class2_set.has(0)))
return;
unsigned len1 = valueFormat1.get_len ();
unsigned len2 = valueFormat2.get_len ();
const hb_array_t<const Value> values_array = values.as_array ((unsigned)class1Count * (unsigned) class2Count * (len1 + len2));
for (const unsigned class1_idx : class1_set.iter ())
{
for (const unsigned class2_idx : class2_set.iter ())
{
unsigned start_offset = (class1_idx * (unsigned) class2Count + class2_idx) * (len1 + len2);
if (valueFormat1.has_device ())
valueFormat1.collect_variation_indices (c, this, values_array.sub_array (start_offset, len1));
if (valueFormat2.has_device ())
valueFormat2.collect_variation_indices (c, this, values_array.sub_array (start_offset+len1, len2));
}
}
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
if (unlikely (!(this+classDef2).collect_coverage (c->input))) return;
}
const Coverage &get_coverage () const { return this+coverage; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset_fast (buffer->idx);
unsigned unsafe_to;
if (unlikely (!skippy_iter.next (&unsafe_to)))
{
buffer->unsafe_to_concat (buffer->idx, unsafe_to);
return_trace (false);
}
unsigned int klass1 = (this+classDef1).get_class (buffer->cur().codepoint);
unsigned int klass2 = (this+classDef2).get_class (buffer->info[skippy_iter.idx].codepoint);
if (unlikely (klass1 >= class1Count || klass2 >= class2Count))
{
buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
return_trace (false);
}
unsigned int len1 = valueFormat1.get_len ();
unsigned int len2 = valueFormat2.get_len ();
unsigned int record_len = len1 + len2;
const Value *v = &values[record_len * (klass1 * class2Count + klass2)];
bool applied_first = false, applied_second = false;
/* Isolate simple kerning and apply it half to each side.
* Results in better cursor positioning / underline drawing.
*
* Disabled, because causes issues... :-(
* https://github.com/harfbuzz/harfbuzz/issues/3408
* https://github.com/harfbuzz/harfbuzz/pull/3235#issuecomment-1029814978
*/
#ifndef HB_SPLIT_KERN
if (false)
#endif
{
if (!len2)
{
const hb_direction_t dir = buffer->props.direction;
const bool horizontal = HB_DIRECTION_IS_HORIZONTAL (dir);
const bool backward = HB_DIRECTION_IS_BACKWARD (dir);
unsigned mask = horizontal ? ValueFormat::xAdvance : ValueFormat::yAdvance;
if (backward)
mask |= mask >> 2; /* Add eg. xPlacement in RTL. */
/* Add Devices. */
mask |= mask << 4;
if (valueFormat1 & ~mask)
goto bail;
/* Is simple kern. Apply value on an empty position slot,
* then split it between sides. */
hb_glyph_position_t pos{};
if (valueFormat1.apply_value (c, this, v, pos))
{
hb_position_t *src = &pos.x_advance;
hb_position_t *dst1 = &buffer->cur_pos().x_advance;
hb_position_t *dst2 = &buffer->pos[skippy_iter.idx].x_advance;
unsigned i = horizontal ? 0 : 1;
hb_position_t kern = src[i];
hb_position_t kern1 = kern >> 1;
hb_position_t kern2 = kern - kern1;
if (!backward)
{
dst1[i] += kern1;
dst2[i] += kern2;
dst2[i + 2] += kern2;
}
else
{
dst1[i] += kern1;
dst1[i + 2] += src[i + 2] - kern2;
dst2[i] += kern2;
}
applied_first = applied_second = kern != 0;
goto success;
}
goto boring;
}
}
bail:
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"try kerning glyphs at %u,%u",
c->buffer->idx, skippy_iter.idx);
}
applied_first = len1 && valueFormat1.apply_value (c, this, v, buffer->cur_pos());
applied_second = len2 && valueFormat2.apply_value (c, this, v + len1, buffer->pos[skippy_iter.idx]);
if (applied_first || applied_second)
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"kerned glyphs at %u,%u",
c->buffer->idx, skippy_iter.idx);
}
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"tried kerning glyphs at %u,%u",
c->buffer->idx, skippy_iter.idx);
}
success:
if (applied_first || applied_second)
buffer->unsafe_to_break (buffer->idx, skippy_iter.idx + 1);
else
boring:
buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
if (len2)
{
skippy_iter.idx++;
// https://github.com/harfbuzz/harfbuzz/issues/3824
// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
buffer->unsafe_to_break (buffer->idx, skippy_iter.idx + 1);
}
buffer->idx = skippy_iter.idx;
return_trace (true);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_map_t klass1_map;
out->classDef1.serialize_subset (c, classDef1, this, &klass1_map, true, true, &(this + coverage));
out->class1Count = klass1_map.get_population ();
hb_map_t klass2_map;
out->classDef2.serialize_subset (c, classDef2, this, &klass2_map, true, false);
out->class2Count = klass2_map.get_population ();
unsigned len1 = valueFormat1.get_len ();
unsigned len2 = valueFormat2.get_len ();
hb_pair_t<unsigned, unsigned> newFormats = hb_pair (valueFormat1, valueFormat2);
if (c->plan->normalized_coords)
{
/* in case of full instancing, all var device flags will be dropped so no
* need to strip hints here */
newFormats = compute_effective_value_formats (klass1_map, klass2_map, false, false, &c->plan->layout_variation_idx_delta_map);
}
/* do not strip hints for VF */
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
{
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
bool has_fvar = (blob != hb_blob_get_empty ());
hb_blob_destroy (blob);
bool strip = !has_fvar;
/* special case: strip hints when a VF has no GDEF varstore after
* subsetting*/
if (has_fvar && !c->plan->has_gdef_varstore)
strip = true;
newFormats = compute_effective_value_formats (klass1_map, klass2_map, strip, true);
}
out->valueFormat1 = newFormats.first;
out->valueFormat2 = newFormats.second;
unsigned total_len = len1 + len2;
hb_vector_t<unsigned> class2_idxs (+ hb_range ((unsigned) class2Count) | hb_filter (klass2_map));
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
{
for (unsigned class2_idx : class2_idxs)
{
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * total_len;
valueFormat1.copy_values (c->serializer, out->valueFormat1, this, &values[idx], &c->plan->layout_variation_idx_delta_map);
valueFormat2.copy_values (c->serializer, out->valueFormat2, this, &values[idx + len1], &c->plan->layout_variation_idx_delta_map);
}
}
bool ret = out->coverage.serialize_subset(c, coverage, this);
return_trace (out->class1Count && out->class2Count && ret);
}
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_map_t& klass1_map,
const hb_map_t& klass2_map,
bool strip_hints, bool strip_empty,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map = nullptr) const
{
unsigned len1 = valueFormat1.get_len ();
unsigned len2 = valueFormat2.get_len ();
unsigned record_size = len1 + len2;
unsigned format1 = 0;
unsigned format2 = 0;
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
{
for (unsigned class2_idx : + hb_range ((unsigned) class2Count) | hb_filter (klass2_map))
{
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * record_size;
format1 = format1 | valueFormat1.get_effective_format (&values[idx], strip_hints, strip_empty, this, varidx_delta_map);
format2 = format2 | valueFormat2.get_effective_format (&values[idx + len1], strip_hints, strip_empty, this, varidx_delta_map);
}
if (format1 == valueFormat1 && format2 == valueFormat2)
break;
}
return hb_pair (format1, format2);
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
@@ -0,0 +1,210 @@
#ifndef OT_LAYOUT_GPOS_PAIRSET_HH
#define OT_LAYOUT_GPOS_PAIRSET_HH
#include "PairValueRecord.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
template <typename Types>
struct PairSet : ValueBase
{
template <typename Types2>
friend struct PairPosFormat1_3;
using PairValueRecord = GPOS_impl::PairValueRecord<Types>;
protected:
HBUINT16 len; /* Number of PairValueRecords */
PairValueRecord firstPairValueRecord;
/* Array of PairValueRecords--ordered
* by GlyphID of the second glyph */
public:
DEFINE_SIZE_MIN (2);
static unsigned get_size (unsigned len1, unsigned len2)
{
return Types::HBGlyphID::static_size + Value::static_size * (len1 + len2);
}
static unsigned get_size (const ValueFormat valueFormats[2])
{
unsigned len1 = valueFormats[0].get_len ();
unsigned len2 = valueFormats[1].get_len ();
return get_size (len1, len2);
}
struct sanitize_closure_t
{
const ValueFormat *valueFormats;
unsigned int len1; /* valueFormats[0].get_len() */
unsigned int stride; /* bytes */
};
bool sanitize (hb_sanitize_context_t *c, const sanitize_closure_t *closure) const
{
TRACE_SANITIZE (this);
if (!(c->check_struct (this) &&
hb_barrier () &&
c->check_range (&firstPairValueRecord,
len,
closure->stride))) return_trace (false);
hb_barrier ();
unsigned int count = len;
const PairValueRecord *record = &firstPairValueRecord;
return_trace (c->lazy_some_gpos ||
(closure->valueFormats[0].sanitize_values_stride_unsafe (c, this, &record->values[0], count, closure->stride) &&
closure->valueFormats[1].sanitize_values_stride_unsafe (c, this, &record->values[closure->len1], count, closure->stride)));
}
bool intersects (const hb_set_t *glyphs,
const ValueFormat *valueFormats) const
{
unsigned record_size = get_size (valueFormats);
const PairValueRecord *record = &firstPairValueRecord;
unsigned int count = len;
for (unsigned int i = 0; i < count; i++)
{
if (glyphs->has (record->secondGlyph))
return true;
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
return false;
}
void collect_glyphs (hb_collect_glyphs_context_t *c,
const ValueFormat *valueFormats) const
{
unsigned record_size = get_size (valueFormats);
const PairValueRecord *record = &firstPairValueRecord;
c->input->add_array (&record->secondGlyph, len, record_size);
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const ValueFormat *valueFormats) const
{
unsigned record_size = get_size (valueFormats);
const PairValueRecord *record = &firstPairValueRecord;
unsigned count = len;
for (unsigned i = 0; i < count; i++)
{
if (c->glyph_set->has (record->secondGlyph))
{ record->collect_variation_indices (c, valueFormats, this); }
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
}
bool apply (hb_ot_apply_context_t *c,
const ValueFormat *valueFormats,
unsigned int pos) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int len1 = valueFormats[0].get_len ();
unsigned int len2 = valueFormats[1].get_len ();
unsigned record_size = get_size (len1, len2);
const PairValueRecord *record = hb_bsearch (buffer->info[pos].codepoint,
&firstPairValueRecord,
len,
record_size);
if (record)
{
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"try kerning glyphs at %u,%u",
c->buffer->idx, pos);
}
bool applied_first = len1 && valueFormats[0].apply_value (c, this, &record->values[0], buffer->cur_pos());
bool applied_second = len2 && valueFormats[1].apply_value (c, this, &record->values[len1], buffer->pos[pos]);
if (applied_first || applied_second)
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"kerned glyphs at %u,%u",
c->buffer->idx, pos);
}
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"tried kerning glyphs at %u,%u",
c->buffer->idx, pos);
}
if (applied_first || applied_second)
buffer->unsafe_to_break (buffer->idx, pos + 1);
if (len2)
{
pos++;
// https://github.com/harfbuzz/harfbuzz/issues/3824
// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
buffer->unsafe_to_break (buffer->idx, pos + 1);
}
buffer->idx = pos;
return_trace (true);
}
buffer->unsafe_to_concat (buffer->idx, pos + 1);
return_trace (false);
}
bool subset (hb_subset_context_t *c,
const ValueFormat valueFormats[2],
const ValueFormat newFormats[2]) const
{
TRACE_SUBSET (this);
auto snap = c->serializer->snapshot ();
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->len = 0;
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
unsigned len1 = valueFormats[0].get_len ();
unsigned len2 = valueFormats[1].get_len ();
unsigned record_size = get_size (len1, len2);
typename PairValueRecord::context_t context =
{
this,
valueFormats,
newFormats,
len1,
&glyph_map,
&c->plan->layout_variation_idx_delta_map
};
const PairValueRecord *record = &firstPairValueRecord;
unsigned count = len, num = 0;
for (unsigned i = 0; i < count; i++)
{
if (glyphset.has (record->secondGlyph)
&& record->subset (c, &context)) num++;
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
out->len = num;
if (!num) c->serializer->revert (snap);
return_trace (num);
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_PAIRSET_HH
@@ -0,0 +1,99 @@
#ifndef OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
#define OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
#include "ValueFormat.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
template <typename Types>
struct PairValueRecord
{
template <typename Types2>
friend struct PairSet;
protected:
typename Types::HBGlyphID
secondGlyph; /* GlyphID of second glyph in the
* pair--first glyph is listed in the
* Coverage table */
ValueRecord values; /* Positioning data for the first glyph
* followed by for second glyph */
public:
DEFINE_SIZE_ARRAY (Types::HBGlyphID::static_size, values);
int cmp (hb_codepoint_t k) const
{ return secondGlyph.cmp (k); }
struct context_t
{
const ValueBase *base;
const ValueFormat *valueFormats;
const ValueFormat *newFormats;
unsigned len1; /* valueFormats[0].get_len() */
const hb_map_t *glyph_map;
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map;
};
bool subset (hb_subset_context_t *c,
context_t *closure) const
{
TRACE_SERIALIZE (this);
auto *s = c->serializer;
auto *out = s->start_embed (*this);
if (unlikely (!s->extend_min (out))) return_trace (false);
out->secondGlyph = (*closure->glyph_map)[secondGlyph];
closure->valueFormats[0].copy_values (s,
closure->newFormats[0],
closure->base, &values[0],
closure->layout_variation_idx_delta_map);
closure->valueFormats[1].copy_values (s,
closure->newFormats[1],
closure->base,
&values[closure->len1],
closure->layout_variation_idx_delta_map);
return_trace (true);
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const ValueFormat *valueFormats,
const ValueBase *base) const
{
unsigned record1_len = valueFormats[0].get_len ();
unsigned record2_len = valueFormats[1].get_len ();
const hb_array_t<const Value> values_array = values.as_array (record1_len + record2_len);
if (valueFormats[0].has_device ())
valueFormats[0].collect_variation_indices (c, base, values_array.sub_array (0, record1_len));
if (valueFormats[1].has_device ())
valueFormats[1].collect_variation_indices (c, base, values_array.sub_array (record1_len, record2_len));
}
bool intersects (const hb_set_t& glyphset) const
{
return glyphset.has(secondGlyph);
}
const Value* get_values_1 () const
{
return &values[0];
}
const Value* get_values_2 (ValueFormat format1) const
{
return &values[format1.get_len ()];
}
};
}
}
}
#endif // OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
@@ -0,0 +1,79 @@
#ifndef OT_LAYOUT_GPOS_POSLOOKUP_HH
#define OT_LAYOUT_GPOS_POSLOOKUP_HH
#include "PosLookupSubTable.hh"
#include "../../../hb-ot-layout-common.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct PosLookup : Lookup
{
using SubTable = PosLookupSubTable;
const SubTable& get_subtable (unsigned int i) const
{ return Lookup::get_subtable<SubTable> (i); }
bool is_reverse () const
{
return false;
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
return_trace (dispatch (c));
}
bool intersects (const hb_set_t *glyphs) const
{
hb_intersects_context_t c (glyphs);
return dispatch (&c);
}
hb_collect_glyphs_context_t::return_t collect_glyphs (hb_collect_glyphs_context_t *c) const
{ return dispatch (c); }
hb_closure_lookups_context_t::return_t closure_lookups (hb_closure_lookups_context_t *c, unsigned this_index) const
{
if (c->is_lookup_visited (this_index))
return hb_closure_lookups_context_t::default_return_value ();
c->set_lookup_visited (this_index);
if (!intersects (c->glyphs))
{
c->set_lookup_inactive (this_index);
return hb_closure_lookups_context_t::default_return_value ();
}
hb_closure_lookups_context_t::return_t ret = dispatch (c);
return ret;
}
template <typename set_t>
void collect_coverage (set_t *glyphs) const
{
hb_collect_coverage_context_t<set_t> c (glyphs);
dispatch (&c);
}
template <typename context_t>
static typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{ return Lookup::dispatch<SubTable> (c, std::forward<Ts> (ds)...); }
bool subset (hb_subset_context_t *c) const
{ return Lookup::subset<SubTable> (c); }
bool sanitize (hb_sanitize_context_t *c) const
{ return Lookup::sanitize<SubTable> (c); }
};
}
}
}
#endif /* OT_LAYOUT_GPOS_POSLOOKUP_HH */
@@ -0,0 +1,79 @@
#ifndef OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH
#define OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH
#include "SinglePos.hh"
#include "PairPos.hh"
#include "CursivePos.hh"
#include "MarkBasePos.hh"
#include "MarkLigPos.hh"
#include "MarkMarkPos.hh"
#include "ContextPos.hh"
#include "ChainContextPos.hh"
#include "ExtensionPos.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct PosLookupSubTable
{
friend struct ::OT::Lookup;
friend struct PosLookup;
enum Type {
Single = 1,
Pair = 2,
Cursive = 3,
MarkBase = 4,
MarkLig = 5,
MarkMark = 6,
Context = 7,
ChainContext = 8,
Extension = 9
};
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type, Ts&&... ds) const
{
TRACE_DISPATCH (this, lookup_type);
switch (lookup_type) {
case Single: return_trace (u.single.dispatch (c, std::forward<Ts> (ds)...));
case Pair: return_trace (u.pair.dispatch (c, std::forward<Ts> (ds)...));
case Cursive: return_trace (u.cursive.dispatch (c, std::forward<Ts> (ds)...));
case MarkBase: return_trace (u.markBase.dispatch (c, std::forward<Ts> (ds)...));
case MarkLig: return_trace (u.markLig.dispatch (c, std::forward<Ts> (ds)...));
case MarkMark: return_trace (u.markMark.dispatch (c, std::forward<Ts> (ds)...));
case Context: return_trace (u.context.dispatch (c, std::forward<Ts> (ds)...));
case ChainContext: return_trace (u.chainContext.dispatch (c, std::forward<Ts> (ds)...));
case Extension: return_trace (u.extension.dispatch (c, std::forward<Ts> (ds)...));
default: return_trace (c->default_return_value ());
}
}
bool intersects (const hb_set_t *glyphs, unsigned int lookup_type) const
{
hb_intersects_context_t c (glyphs);
return dispatch (&c, lookup_type);
}
protected:
union {
SinglePos single;
PairPos pair;
CursivePos cursive;
MarkBasePos markBase;
MarkLigPos markLig;
MarkMarkPos markMark;
ContextPos context;
ChainContextPos chainContext;
ExtensionPos extension;
} u;
public:
DEFINE_SIZE_MIN (0);
};
}
}
}
#endif /* HB_OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH */
@@ -0,0 +1,98 @@
#ifndef OT_LAYOUT_GPOS_SINGLEPOS_HH
#define OT_LAYOUT_GPOS_SINGLEPOS_HH
#include "SinglePosFormat1.hh"
#include "SinglePosFormat2.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct SinglePos
{
protected:
union {
HBUINT16 format; /* Format identifier */
SinglePosFormat1 format1;
SinglePosFormat2 format2;
} u;
public:
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
unsigned get_format (Iterator glyph_val_iter_pairs)
{
hb_array_t<const Value> first_val_iter = hb_second (*glyph_val_iter_pairs);
for (const auto iter : glyph_val_iter_pairs)
for (const auto _ : hb_zip (iter.second, first_val_iter))
if (_.first != _.second)
return 2;
return 1;
}
template<typename Iterator,
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const SrcLookup* src,
Iterator glyph_val_iter_pairs,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
unsigned newFormat)
{
if (unlikely (!c->extend_min (u.format))) return;
unsigned format = 2;
ValueFormat new_format;
new_format = newFormat;
if (glyph_val_iter_pairs)
format = get_format (glyph_val_iter_pairs);
u.format = format;
switch (u.format) {
case 1: u.format1.serialize (c,
src,
glyph_val_iter_pairs,
new_format,
layout_variation_idx_delta_map);
return;
case 2: u.format2.serialize (c,
src,
glyph_val_iter_pairs,
new_format,
layout_variation_idx_delta_map);
return;
default:return;
}
}
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
};
template<typename Iterator, typename SrcLookup>
static void
SinglePos_serialize (hb_serialize_context_t *c,
const SrcLookup *src,
Iterator it,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
unsigned new_format)
{ c->start_embed<SinglePos> ()->serialize (c, src, it, layout_variation_idx_delta_map, new_format); }
}
}
}
#endif /* OT_LAYOUT_GPOS_SINGLEPOS_HH */
@@ -0,0 +1,190 @@
#ifndef OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH
#define OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH
#include "Common.hh"
#include "ValueFormat.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct SinglePosFormat1 : ValueBase
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
Offset16To<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of subtable */
ValueFormat valueFormat; /* Defines the types of data in the
* ValueRecord */
ValueRecord values; /* Defines positioning
* value(s)--applied to all glyphs in
* the Coverage table */
public:
DEFINE_SIZE_ARRAY (6, values);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
coverage.sanitize (c, this) &&
hb_barrier () &&
/* The coverage table may use a range to represent a set
* of glyphs, which means a small number of bytes can
* generate a large glyph set. Manually modify the
* sanitizer max ops to take this into account.
*
* Note: This check *must* be right after coverage sanitize. */
c->check_ops ((this + coverage).get_population () >> 1) &&
valueFormat.sanitize_value (c, this, values));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
if (!valueFormat.has_device ()) return;
hb_set_t intersection;
(this+coverage).intersect_set (*c->glyph_set, intersection);
if (!intersection) return;
valueFormat.collect_variation_indices (c, this, values.as_array (valueFormat.get_len ()));
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
const Coverage &get_coverage () const { return this+coverage; }
ValueFormat get_value_format () const { return valueFormat; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"positioning glyph at %u",
c->buffer->idx);
}
valueFormat.apply_value (c, this, values, buffer->cur_pos());
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"positioned glyph at %u",
c->buffer->idx);
}
buffer->idx++;
return_trace (true);
}
bool
position_single (hb_font_t *font,
hb_blob_t *table_blob,
hb_direction_t direction,
hb_codepoint_t gid,
hb_glyph_position_t &pos) const
{
unsigned int index = (this+coverage).get_coverage (gid);
if (likely (index == NOT_COVERED)) return false;
/* This is ugly... */
hb_buffer_t buffer;
buffer.props.direction = direction;
OT::hb_ot_apply_context_t c (1, font, &buffer, table_blob);
valueFormat.apply_value (&c, this, values, pos);
return true;
}
template<typename Iterator,
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const SrcLookup *src,
Iterator it,
ValueFormat newFormat,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map)
{
if (unlikely (!c->extend_min (this))) return;
if (unlikely (!c->check_assign (valueFormat,
newFormat,
HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
for (const hb_array_t<const Value>& _ : + it | hb_map (hb_second))
{
src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_delta_map);
// Only serialize the first entry in the iterator, the rest are assumed to
// be the same.
break;
}
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
coverage.serialize_serialize (c, glyphs);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
hb_set_t intersection;
(this+coverage).intersect_set (glyphset, intersection);
unsigned new_format = valueFormat;
if (c->plan->normalized_coords)
{
new_format = valueFormat.get_effective_format (values.arrayZ, false, false, this, &c->plan->layout_variation_idx_delta_map);
}
/* do not strip hints for VF */
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
{
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
bool has_fvar = (blob != hb_blob_get_empty ());
hb_blob_destroy (blob);
bool strip = !has_fvar;
/* special case: strip hints when a VF has no GDEF varstore after
* subsetting*/
if (has_fvar && !c->plan->has_gdef_varstore)
strip = true;
new_format = valueFormat.get_effective_format (values.arrayZ,
strip, /* strip hints */
true, /* strip empty */
this, nullptr);
}
auto it =
+ hb_iter (intersection)
| hb_map_retains_sorting (glyph_map)
| hb_zip (hb_repeat (values.as_array (valueFormat.get_len ())))
;
bool ret = bool (it);
SinglePos_serialize (c->serializer, this, it, &c->plan->layout_variation_idx_delta_map, new_format);
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH */
@@ -0,0 +1,213 @@
#ifndef OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH
#define OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
struct SinglePosFormat2 : ValueBase
{
protected:
HBUINT16 format; /* Format identifier--format = 2 */
Offset16To<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of subtable */
ValueFormat valueFormat; /* Defines the types of data in the
* ValueRecord */
HBUINT16 valueCount; /* Number of ValueRecords */
ValueRecord values; /* Array of ValueRecords--positioning
* values applied to glyphs */
public:
DEFINE_SIZE_ARRAY (8, values);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
coverage.sanitize (c, this) &&
valueFormat.sanitize_values (c, this, values, valueCount));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
if (!valueFormat.has_device ()) return;
auto it =
+ hb_zip (this+coverage, hb_range ((unsigned) valueCount))
| hb_filter (c->glyph_set, hb_first)
;
if (!it) return;
unsigned sub_length = valueFormat.get_len ();
const hb_array_t<const Value> values_array = values.as_array (valueCount * sub_length);
for (unsigned i : + it
| hb_map (hb_second))
valueFormat.collect_variation_indices (c, this, values_array.sub_array (i * sub_length, sub_length));
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
const Coverage &get_coverage () const { return this+coverage; }
ValueFormat get_value_format () const { return valueFormat; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_buffer_t *buffer = c->buffer;
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
if (unlikely (index >= valueCount)) return_trace (false);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"positioning glyph at %u",
c->buffer->idx);
}
valueFormat.apply_value (c, this,
&values[index * valueFormat.get_len ()],
buffer->cur_pos());
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"positioned glyph at %u",
c->buffer->idx);
}
buffer->idx++;
return_trace (true);
}
bool
position_single (hb_font_t *font,
hb_blob_t *table_blob,
hb_direction_t direction,
hb_codepoint_t gid,
hb_glyph_position_t &pos) const
{
unsigned int index = (this+coverage).get_coverage (gid);
if (likely (index == NOT_COVERED)) return false;
if (unlikely (index >= valueCount)) return false;
/* This is ugly... */
hb_buffer_t buffer;
buffer.props.direction = direction;
OT::hb_ot_apply_context_t c (1, font, &buffer, table_blob);
valueFormat.apply_value (&c, this,
&values[index * valueFormat.get_len ()],
pos);
return true;
}
template<typename Iterator,
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const SrcLookup *src,
Iterator it,
ValueFormat newFormat,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map)
{
auto out = c->extend_min (this);
if (unlikely (!out)) return;
if (unlikely (!c->check_assign (valueFormat, newFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERROR_ARRAY_OVERFLOW))) return;
+ it
| hb_map (hb_second)
| hb_apply ([&] (hb_array_t<const Value> _)
{ src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_delta_map); })
;
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
coverage.serialize_serialize (c, glyphs);
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
unsigned compute_effective_format (const hb_face_t *face,
Iterator it,
bool is_instancing, bool strip_hints,
bool has_gdef_varstore,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
{
hb_blob_t* blob = hb_face_reference_table (face, HB_TAG ('f','v','a','r'));
bool has_fvar = (blob != hb_blob_get_empty ());
hb_blob_destroy (blob);
unsigned new_format = 0;
if (is_instancing)
{
new_format = new_format | valueFormat.get_effective_format (+ it | hb_map (hb_second), false, false, this, varidx_delta_map);
}
/* do not strip hints for VF */
else if (strip_hints)
{
bool strip = !has_fvar;
if (has_fvar && !has_gdef_varstore)
strip = true;
new_format = new_format | valueFormat.get_effective_format (+ it | hb_map (hb_second), strip, true, this, nullptr);
}
else
new_format = valueFormat;
return new_format;
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
unsigned sub_length = valueFormat.get_len ();
auto values_array = values.as_array (valueCount * sub_length);
auto it =
+ hb_zip (this+coverage, hb_range ((unsigned) valueCount))
| hb_filter (glyphset, hb_first)
| hb_map_retains_sorting ([&] (const hb_pair_t<hb_codepoint_t, unsigned>& _)
{
return hb_pair (glyph_map[_.first],
values_array.sub_array (_.second * sub_length,
sub_length));
})
;
unsigned new_format = compute_effective_format (c->plan->source, it,
bool (c->plan->normalized_coords),
bool (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING),
c->plan->has_gdef_varstore,
&c->plan->layout_variation_idx_delta_map);
bool ret = bool (it);
SinglePos_serialize (c->serializer, this, it, &c->plan->layout_variation_idx_delta_map, new_format);
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH */
@@ -0,0 +1,441 @@
#ifndef OT_LAYOUT_GPOS_VALUEFORMAT_HH
#define OT_LAYOUT_GPOS_VALUEFORMAT_HH
#include "../../../hb-ot-layout-gsubgpos.hh"
namespace OT {
namespace Layout {
namespace GPOS_impl {
typedef HBUINT16 Value;
struct ValueBase {}; // Dummy base class tag for OffsetTo<Value> bases.
typedef UnsizedArrayOf<Value> ValueRecord;
struct ValueFormat : HBUINT16
{
enum Flags {
xPlacement = 0x0001u, /* Includes horizontal adjustment for placement */
yPlacement = 0x0002u, /* Includes vertical adjustment for placement */
xAdvance = 0x0004u, /* Includes horizontal adjustment for advance */
yAdvance = 0x0008u, /* Includes vertical adjustment for advance */
xPlaDevice = 0x0010u, /* Includes horizontal Device table for placement */
yPlaDevice = 0x0020u, /* Includes vertical Device table for placement */
xAdvDevice = 0x0040u, /* Includes horizontal Device table for advance */
yAdvDevice = 0x0080u, /* Includes vertical Device table for advance */
ignored = 0x0F00u, /* Was used in TrueType Open for MM fonts */
reserved = 0xF000u, /* For future use */
devices = 0x00F0u /* Mask for having any Device table */
};
/* All fields are options. Only those available advance the value pointer. */
#if 0
HBINT16 xPlacement; /* Horizontal adjustment for
* placement--in design units */
HBINT16 yPlacement; /* Vertical adjustment for
* placement--in design units */
HBINT16 xAdvance; /* Horizontal adjustment for
* advance--in design units (only used
* for horizontal writing) */
HBINT16 yAdvance; /* Vertical adjustment for advance--in
* design units (only used for vertical
* writing) */
Offset16To<Device> xPlaDevice; /* Offset to Device table for
* horizontal placement--measured from
* beginning of PosTable (may be NULL) */
Offset16To<Device> yPlaDevice; /* Offset to Device table for vertical
* placement--measured from beginning
* of PosTable (may be NULL) */
Offset16To<Device> xAdvDevice; /* Offset to Device table for
* horizontal advance--measured from
* beginning of PosTable (may be NULL) */
Offset16To<Device> yAdvDevice; /* Offset to Device table for vertical
* advance--measured from beginning of
* PosTable (may be NULL) */
#endif
IntType& operator = (uint16_t i) { v = i; return *this; }
unsigned int get_len () const { return hb_popcount ((unsigned int) *this); }
unsigned int get_size () const { return get_len () * Value::static_size; }
hb_vector_t<unsigned> get_device_table_indices () const {
unsigned i = 0;
hb_vector_t<unsigned> result;
unsigned format = *this;
if (format & xPlacement) i++;
if (format & yPlacement) i++;
if (format & xAdvance) i++;
if (format & yAdvance) i++;
if (format & xPlaDevice) result.push (i++);
if (format & yPlaDevice) result.push (i++);
if (format & xAdvDevice) result.push (i++);
if (format & yAdvDevice) result.push (i++);
return result;
}
bool apply_value (hb_ot_apply_context_t *c,
const ValueBase *base,
const Value *values,
hb_glyph_position_t &glyph_pos) const
{
bool ret = false;
unsigned int format = *this;
if (!format) return ret;
hb_font_t *font = c->font;
bool horizontal =
#ifndef HB_NO_VERTICAL
HB_DIRECTION_IS_HORIZONTAL (c->direction)
#else
true
#endif
;
if (format & xPlacement) glyph_pos.x_offset += font->em_scale_x (get_short (values++, &ret));
if (format & yPlacement) glyph_pos.y_offset += font->em_scale_y (get_short (values++, &ret));
if (format & xAdvance) {
if (likely (horizontal)) glyph_pos.x_advance += font->em_scale_x (get_short (values, &ret));
values++;
}
/* y_advance values grow downward but font-space grows upward, hence negation */
if (format & yAdvance) {
if (unlikely (!horizontal)) glyph_pos.y_advance -= font->em_scale_y (get_short (values, &ret));
values++;
}
if (!has_device ()) return ret;
bool use_x_device = font->x_ppem || font->num_coords;
bool use_y_device = font->y_ppem || font->num_coords;
if (!use_x_device && !use_y_device) return ret;
const ItemVariationStore &store = c->var_store;
auto *cache = c->var_store_cache;
/* pixel -> fractional pixel */
if (format & xPlaDevice)
{
if (use_x_device) glyph_pos.x_offset += get_device (values, &ret, base, c->sanitizer).get_x_delta (font, store, cache);
values++;
}
if (format & yPlaDevice)
{
if (use_y_device) glyph_pos.y_offset += get_device (values, &ret, base, c->sanitizer).get_y_delta (font, store, cache);
values++;
}
if (format & xAdvDevice)
{
if (horizontal && use_x_device) glyph_pos.x_advance += get_device (values, &ret, base, c->sanitizer).get_x_delta (font, store, cache);
values++;
}
if (format & yAdvDevice)
{
/* y_advance values grow downward but font-space grows upward, hence negation */
if (!horizontal && use_y_device) glyph_pos.y_advance -= get_device (values, &ret, base, c->sanitizer).get_y_delta (font, store, cache);
values++;
}
return ret;
}
unsigned int get_effective_format (const Value *values, bool strip_hints, bool strip_empty, const ValueBase *base,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
{
unsigned int format = *this;
for (unsigned flag = xPlacement; flag <= yAdvDevice; flag = flag << 1) {
if (format & flag)
{
if (strip_hints && flag >= xPlaDevice)
{
format = format & ~flag;
values++;
continue;
}
if (varidx_delta_map && flag >= xPlaDevice)
{
update_var_flag (values++, (Flags) flag, &format, base, varidx_delta_map);
continue;
}
/* do not strip empty when instancing, cause we don't know whether the new
* default value is 0 or not */
if (strip_empty) should_drop (*values, (Flags) flag, &format);
values++;
}
}
return format;
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
unsigned int get_effective_format (Iterator it, bool strip_hints, bool strip_empty, const ValueBase *base,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const {
unsigned int new_format = 0;
for (const hb_array_t<const Value>& values : it)
new_format = new_format | get_effective_format (&values, strip_hints, strip_empty, base, varidx_delta_map);
return new_format;
}
void copy_values (hb_serialize_context_t *c,
unsigned int new_format,
const ValueBase *base,
const Value *values,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map) const
{
unsigned int format = *this;
if (!format) return;
HBINT16 *x_placement = nullptr, *y_placement = nullptr, *x_adv = nullptr, *y_adv = nullptr;
if (format & xPlacement) x_placement = copy_value (c, new_format, xPlacement, *values++);
if (format & yPlacement) y_placement = copy_value (c, new_format, yPlacement, *values++);
if (format & xAdvance) x_adv = copy_value (c, new_format, xAdvance, *values++);
if (format & yAdvance) y_adv = copy_value (c, new_format, yAdvance, *values++);
if (!has_device ())
return;
if (format & xPlaDevice)
{
add_delta_to_value (x_placement, base, values, layout_variation_idx_delta_map);
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, xPlaDevice);
}
if (format & yPlaDevice)
{
add_delta_to_value (y_placement, base, values, layout_variation_idx_delta_map);
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, yPlaDevice);
}
if (format & xAdvDevice)
{
add_delta_to_value (x_adv, base, values, layout_variation_idx_delta_map);
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, xAdvDevice);
}
if (format & yAdvDevice)
{
add_delta_to_value (y_adv, base, values, layout_variation_idx_delta_map);
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, yAdvDevice);
}
}
HBINT16* copy_value (hb_serialize_context_t *c,
unsigned int new_format,
Flags flag,
Value value) const
{
// Filter by new format.
if (!(new_format & flag)) return nullptr;
return reinterpret_cast<HBINT16 *> (c->copy (value));
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const ValueBase *base,
const hb_array_t<const Value>& values) const
{
unsigned format = *this;
unsigned i = 0;
if (format & xPlacement) i++;
if (format & yPlacement) i++;
if (format & xAdvance) i++;
if (format & yAdvance) i++;
if (format & xPlaDevice)
{
(base + get_device (&(values[i]))).collect_variation_indices (c);
i++;
}
if (format & ValueFormat::yPlaDevice)
{
(base + get_device (&(values[i]))).collect_variation_indices (c);
i++;
}
if (format & ValueFormat::xAdvDevice)
{
(base + get_device (&(values[i]))).collect_variation_indices (c);
i++;
}
if (format & ValueFormat::yAdvDevice)
{
(base + get_device (&(values[i]))).collect_variation_indices (c);
i++;
}
}
private:
bool sanitize_value_devices (hb_sanitize_context_t *c, const ValueBase *base, const Value *values) const
{
unsigned int format = *this;
if (format & xPlacement) values++;
if (format & yPlacement) values++;
if (format & xAdvance) values++;
if (format & yAdvance) values++;
if ((format & xPlaDevice) && !get_device (values++).sanitize (c, base)) return false;
if ((format & yPlaDevice) && !get_device (values++).sanitize (c, base)) return false;
if ((format & xAdvDevice) && !get_device (values++).sanitize (c, base)) return false;
if ((format & yAdvDevice) && !get_device (values++).sanitize (c, base)) return false;
return true;
}
static inline Offset16To<Device, ValueBase>& get_device (Value* value)
{
return *static_cast<Offset16To<Device, ValueBase> *> (value);
}
static inline const Offset16To<Device, ValueBase>& get_device (const Value* value)
{
return *static_cast<const Offset16To<Device, ValueBase> *> (value);
}
static inline const Device& get_device (const Value* value,
bool *worked,
const ValueBase *base,
hb_sanitize_context_t &c)
{
if (worked) *worked |= bool (*value);
auto &offset = *static_cast<const Offset16To<Device> *> (value);
if (unlikely (!offset.sanitize (&c, base)))
return Null(Device);
hb_barrier ();
return base + offset;
}
void add_delta_to_value (HBINT16 *value,
const ValueBase *base,
const Value *src_value,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map) const
{
if (!value) return;
unsigned varidx = (base + get_device (src_value)).get_variation_index ();
hb_pair_t<unsigned, int> *varidx_delta;
if (!layout_variation_idx_delta_map->has (varidx, &varidx_delta)) return;
*value += hb_second (*varidx_delta);
}
bool copy_device (hb_serialize_context_t *c,
const ValueBase *base,
const Value *src_value,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
unsigned int new_format, Flags flag) const
{
// Filter by new format.
if (!(new_format & flag)) return true;
Value *dst_value = c->copy (*src_value);
if (!dst_value) return false;
if (*dst_value == 0) return true;
*dst_value = 0;
c->push ();
if ((base + get_device (src_value)).copy (c, layout_variation_idx_delta_map))
{
c->add_link (*dst_value, c->pop_pack ());
return true;
}
else
{
c->pop_discard ();
return false;
}
}
static inline const HBINT16& get_short (const Value* value, bool *worked=nullptr)
{
if (worked) *worked |= bool (*value);
return *reinterpret_cast<const HBINT16 *> (value);
}
public:
bool has_device () const
{
unsigned int format = *this;
return (format & devices) != 0;
}
bool sanitize_value (hb_sanitize_context_t *c, const ValueBase *base, const Value *values) const
{
TRACE_SANITIZE (this);
if (unlikely (!c->check_range (values, get_size ()))) return_trace (false);
if (c->lazy_some_gpos)
return_trace (true);
return_trace (!has_device () || sanitize_value_devices (c, base, values));
}
bool sanitize_values (hb_sanitize_context_t *c, const ValueBase *base, const Value *values, unsigned int count) const
{
TRACE_SANITIZE (this);
unsigned size = get_size ();
if (!c->check_range (values, count, size)) return_trace (false);
if (c->lazy_some_gpos)
return_trace (true);
hb_barrier ();
return_trace (sanitize_values_stride_unsafe (c, base, values, count, size));
}
/* Just sanitize referenced Device tables. Doesn't check the values themselves. */
bool sanitize_values_stride_unsafe (hb_sanitize_context_t *c, const ValueBase *base, const Value *values, unsigned int count, unsigned int stride) const
{
TRACE_SANITIZE (this);
if (!has_device ()) return_trace (true);
for (unsigned int i = 0; i < count; i++) {
if (!sanitize_value_devices (c, base, values))
return_trace (false);
values = &StructAtOffset<const Value> (values, stride);
}
return_trace (true);
}
private:
void should_drop (Value value, Flags flag, unsigned int* format) const
{
if (value) return;
*format = *format & ~flag;
}
void update_var_flag (const Value* value, Flags flag,
unsigned int* format, const ValueBase *base,
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
{
if (*value)
{
unsigned varidx = (base + get_device (value)).get_variation_index ();
hb_pair_t<unsigned, int> *varidx_delta;
if (varidx_delta_map->has (varidx, &varidx_delta) &&
varidx_delta->first != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
return;
}
*format = *format & ~flag;
}
};
}
}
}
#endif // #ifndef OT_LAYOUT_GPOS_VALUEFORMAT_HH
@@ -0,0 +1,126 @@
#ifndef OT_LAYOUT_GSUB_ALTERNATESET_HH
#define OT_LAYOUT_GSUB_ALTERNATESET_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct AlternateSet
{
protected:
Array16Of<typename Types::HBGlyphID>
alternates; /* Array of alternate GlyphIDs--in
* arbitrary order */
public:
DEFINE_SIZE_ARRAY (2, alternates);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (alternates.sanitize (c));
}
bool intersects (const hb_set_t *glyphs) const
{ return hb_any (alternates, glyphs); }
void closure (hb_closure_context_t *c) const
{ c->output->add_array (alternates.arrayZ, alternates.len); }
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{ c->output->add_array (alternates.arrayZ, alternates.len); }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int count = alternates.len;
if (unlikely (!count)) return_trace (false);
hb_mask_t glyph_mask = c->buffer->cur().mask;
hb_mask_t lookup_mask = c->lookup_mask;
/* Note: This breaks badly if two features enabled this lookup together. */
unsigned int shift = hb_ctz (lookup_mask);
unsigned int alt_index = ((lookup_mask & glyph_mask) >> shift);
/* If alt_index is MAX_VALUE, randomize feature if it is the rand feature. */
if (alt_index == HB_OT_MAP_MAX_VALUE && c->random)
{
/* Maybe we can do better than unsafe-to-break all; but since we are
* changing random state, it would be hard to track that. Good 'nough. */
c->buffer->unsafe_to_break (0, c->buffer->len);
alt_index = c->random_number () % count + 1;
}
if (unlikely (alt_index > count || alt_index == 0)) return_trace (false);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"replacing glyph at %u (alternate substitution)",
c->buffer->idx);
}
c->replace_glyph (alternates[alt_index - 1]);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (alternate substitution)",
c->buffer->idx - 1u);
}
return_trace (true);
}
unsigned
get_alternates (unsigned start_offset,
unsigned *alternate_count /* IN/OUT. May be NULL. */,
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
{
if (alternates.len && alternate_count)
{
+ alternates.as_array ().sub_array (start_offset, alternate_count)
| hb_sink (hb_array (alternate_glyphs, *alternate_count))
;
}
return alternates.len;
}
template <typename Iterator,
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c,
Iterator alts)
{
TRACE_SERIALIZE (this);
return_trace (alternates.serialize (c, alts));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto it =
+ hb_iter (alternates)
| hb_filter (glyphset)
| hb_map (glyph_map)
;
auto *out = c->serializer->start_embed (*this);
return_trace (out->serialize (c->serializer, it) &&
out->alternates);
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_ALTERNATESET_HH */
@@ -0,0 +1,62 @@
#ifndef OT_LAYOUT_GSUB_ALTERNATESUBST_HH
#define OT_LAYOUT_GSUB_ALTERNATESUBST_HH
#include "AlternateSubstFormat1.hh"
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct AlternateSubst
{
protected:
union {
HBUINT16 format; /* Format identifier */
AlternateSubstFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
AlternateSubstFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
/* TODO This function is unused and not updated to 24bit GIDs. Should be done by using
* iterators. While at it perhaps using iterator of arrays of hb_codepoint_t instead. */
bool serialize (hb_serialize_context_t *c,
hb_sorted_array_t<const HBGlyphID16> glyphs,
hb_array_t<const unsigned int> alternate_len_list,
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (u.format))) return_trace (false);
unsigned int format = 1;
u.format = format;
switch (u.format) {
case 1: return_trace (u.format1.serialize (c, glyphs, alternate_len_list, alternate_glyphs_list));
default:return_trace (false);
}
}
/* TODO subset() should choose format. */
};
}
}
}
#endif /* OT_LAYOUT_GSUB_ALTERNATESUBST_HH */
@@ -0,0 +1,128 @@
#ifndef OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH
#define OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH
#include "AlternateSet.hh"
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct AlternateSubstFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of Substitution table */
Array16Of<typename Types::template OffsetTo<AlternateSet<Types>>>
alternateSet; /* Array of AlternateSet tables
* ordered by Coverage Index */
public:
DEFINE_SIZE_ARRAY (2 + 2 * Types::size, alternateSet);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (coverage.sanitize (c, this) && alternateSet.sanitize (c, this));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
bool may_have_non_1to1 () const
{ return false; }
void closure (hb_closure_context_t *c) const
{
+ hb_zip (this+coverage, alternateSet)
| hb_filter (c->parent_active_glyphs (), hb_first)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const AlternateSet<Types> &_) { _.closure (c); })
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
+ hb_zip (this+coverage, alternateSet)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const AlternateSet<Types> &_) { _.collect_glyphs (c); })
;
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
unsigned
get_glyph_alternates (hb_codepoint_t gid,
unsigned start_offset,
unsigned *alternate_count /* IN/OUT. May be NULL. */,
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
{ return (this+alternateSet[(this+coverage).get_coverage (gid)])
.get_alternates (start_offset, alternate_count, alternate_glyphs); }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
return_trace ((this+alternateSet[index]).apply (c));
}
bool serialize (hb_serialize_context_t *c,
hb_sorted_array_t<const HBGlyphID16> glyphs,
hb_array_t<const unsigned int> alternate_len_list,
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!alternateSet.serialize (c, glyphs.length))) return_trace (false);
for (unsigned int i = 0; i < glyphs.length; i++)
{
unsigned int alternate_len = alternate_len_list[i];
if (unlikely (!alternateSet[i]
.serialize_serialize (c, alternate_glyphs_list.sub_array (0, alternate_len))))
return_trace (false);
alternate_glyphs_list += alternate_len;
}
return_trace (coverage.serialize_serialize (c, glyphs));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
+ hb_zip (this+coverage, alternateSet)
| hb_filter (glyphset, hb_first)
| hb_filter (subset_offset_array (c, out->alternateSet, this), hb_second)
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
return_trace (bool (new_coverage));
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH */
@@ -0,0 +1,18 @@
#ifndef OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH
#define OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH
// TODO(garretrieger): move to new layout.
#include "../../../hb-ot-layout-gsubgpos.hh"
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct ChainContextSubst : ChainContext {};
}
}
}
#endif /* OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH */
@@ -0,0 +1,19 @@
#ifndef OT_LAYOUT_GSUB_COMMON_HH
#define OT_LAYOUT_GSUB_COMMON_HH
#include "../../../hb-serialize.hh"
#include "../../../hb-ot-layout-gsubgpos.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template<typename Iterator>
static void SingleSubst_serialize (hb_serialize_context_t *c,
Iterator it);
}
}
}
#endif /* OT_LAYOUT_GSUB_COMMON_HH */
@@ -0,0 +1,18 @@
#ifndef OT_LAYOUT_GSUB_CONTEXTSUBST_HH
#define OT_LAYOUT_GSUB_CONTEXTSUBST_HH
// TODO(garretrieger): move to new layout.
#include "../../../hb-ot-layout-gsubgpos.hh"
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct ContextSubst : Context {};
}
}
}
#endif /* OT_LAYOUT_GSUB_CONTEXTSUBST_HH */
@@ -0,0 +1,22 @@
#ifndef OT_LAYOUT_GSUB_EXTENSIONSUBST_HH
#define OT_LAYOUT_GSUB_EXTENSIONSUBST_HH
// TODO(garretrieger): move to new layout.
#include "../../../hb-ot-layout-gsubgpos.hh"
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct ExtensionSubst : Extension<ExtensionSubst>
{
typedef struct SubstLookupSubTable SubTable;
bool is_reverse () const;
};
}
}
}
#endif /* OT_LAYOUT_GSUB_EXTENSIONSUBST_HH */
@@ -0,0 +1,61 @@
#ifndef OT_LAYOUT_GSUB_GSUB_HH
#define OT_LAYOUT_GSUB_GSUB_HH
#include "../../../hb-ot-layout-gsubgpos.hh"
#include "Common.hh"
#include "SubstLookup.hh"
namespace OT {
using Layout::GSUB_impl::SubstLookup;
namespace Layout {
/*
* GSUB -- Glyph Substitution
* https://docs.microsoft.com/en-us/typography/opentype/spec/gsub
*/
struct GSUB : GSUBGPOS
{
using Lookup = SubstLookup;
static constexpr hb_tag_t tableTag = HB_OT_TAG_GSUB;
const SubstLookup& get_lookup (unsigned int i) const
{ return static_cast<const SubstLookup &> (GSUBGPOS::get_lookup (i)); }
bool subset (hb_subset_context_t *c) const
{
hb_subset_layout_context_t l (c, tableTag);
return GSUBGPOS::subset<SubstLookup> (&l);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (GSUBGPOS::sanitize<SubstLookup> (c));
}
HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
hb_face_t *face) const;
void closure_lookups (hb_face_t *face,
const hb_set_t *glyphs,
hb_set_t *lookup_indexes /* IN/OUT */) const
{ GSUBGPOS::closure_lookups<SubstLookup> (face, glyphs, lookup_indexes); }
typedef GSUBGPOS::accelerator_t<GSUB> accelerator_t;
};
}
struct GSUB_accelerator_t : Layout::GSUB::accelerator_t {
GSUB_accelerator_t (hb_face_t *face) : Layout::GSUB::accelerator_t (face) {}
};
}
#endif /* OT_LAYOUT_GSUB_GSUB_HH */
@@ -0,0 +1,203 @@
#ifndef OT_LAYOUT_GSUB_LIGATURE_HH
#define OT_LAYOUT_GSUB_LIGATURE_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct Ligature
{
public:
typename Types::HBGlyphID
ligGlyph; /* GlyphID of ligature to substitute */
HeadlessArray16Of<typename Types::HBGlyphID>
component; /* Array of component GlyphIDs--start
* with the second component--ordered
* in writing direction */
public:
DEFINE_SIZE_ARRAY (Types::size + 2, component);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (ligGlyph.sanitize (c) && component.sanitize (c));
}
bool intersects (const hb_set_t *glyphs) const
{ return hb_all (component, glyphs); }
bool intersects_lig_glyph (const hb_set_t *glyphs) const
{ return glyphs->has(ligGlyph); }
void closure (hb_closure_context_t *c) const
{
if (!intersects (c->glyphs)) return;
c->output->add (ligGlyph);
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
c->input->add_array (component.arrayZ, component.get_length ());
c->output->add (ligGlyph);
}
bool would_apply (hb_would_apply_context_t *c) const
{
if (c->len != component.lenP1)
return false;
for (unsigned int i = 1; i < c->len; i++)
if (likely (c->glyphs[i] != component[i]))
return false;
return true;
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int count = component.lenP1;
if (unlikely (!count)) return_trace (false);
/* Special-case to make it in-place and not consider this
* as a "ligated" substitution. */
if (unlikely (count == 1))
{
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"replacing glyph at %u (ligature substitution)",
c->buffer->idx);
}
c->replace_glyph (ligGlyph);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (ligature substitution)",
c->buffer->idx - 1u);
}
return_trace (true);
}
unsigned int total_component_count = 0;
if (unlikely (count > HB_MAX_CONTEXT_LENGTH)) return false;
unsigned match_positions_stack[4];
unsigned *match_positions = match_positions_stack;
if (unlikely (count > ARRAY_LENGTH (match_positions_stack)))
{
match_positions = (unsigned *) hb_malloc (hb_max (count, 1u) * sizeof (unsigned));
if (unlikely (!match_positions))
return_trace (false);
}
unsigned int match_end = 0;
if (likely (!match_input (c, count,
&component[1],
match_glyph,
nullptr,
&match_end,
match_positions,
&total_component_count)))
{
c->buffer->unsafe_to_concat (c->buffer->idx, match_end);
if (match_positions != match_positions_stack)
hb_free (match_positions);
return_trace (false);
}
unsigned pos = 0;
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
unsigned delta = c->buffer->sync_so_far ();
pos = c->buffer->idx;
char buf[HB_MAX_CONTEXT_LENGTH * 16] = {0};
char *p = buf;
match_end += delta;
for (unsigned i = 0; i < count; i++)
{
match_positions[i] += delta;
if (i)
*p++ = ',';
snprintf (p, sizeof(buf) - (p - buf), "%u", match_positions[i]);
p += strlen(p);
}
c->buffer->message (c->font,
"ligating glyphs at %s",
buf);
}
ligate_input (c,
count,
match_positions,
match_end,
ligGlyph,
total_component_count);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"ligated glyph at %u",
pos);
}
if (match_positions != match_positions_stack)
hb_free (match_positions);
return_trace (true);
}
template <typename Iterator,
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c,
hb_codepoint_t ligature,
Iterator components /* Starting from second */)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
ligGlyph = ligature;
if (unlikely (!component.serialize (c, components))) return_trace (false);
return_trace (true);
}
bool subset (hb_subset_context_t *c, unsigned coverage_idx) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
if (!intersects (&glyphset) || !glyphset.has (ligGlyph)) return_trace (false);
// Ensure Coverage table is always packed after this.
c->serializer->add_virtual_link (coverage_idx);
auto it =
+ hb_iter (component)
| hb_map (glyph_map)
;
auto *out = c->serializer->start_embed (*this);
return_trace (out->serialize (c->serializer,
glyph_map[ligGlyph],
it)); }
};
}
}
}
#endif /* OT_LAYOUT_GSUB_LIGATURE_HH */
@@ -0,0 +1,188 @@
#ifndef OT_LAYOUT_GSUB_LIGATURESET_HH
#define OT_LAYOUT_GSUB_LIGATURESET_HH
#include "Common.hh"
#include "Ligature.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct LigatureSet
{
protected:
Array16OfOffset16To<Ligature<Types>>
ligature; /* Array LigatureSet tables
* ordered by preference */
public:
DEFINE_SIZE_ARRAY (2, ligature);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (ligature.sanitize (c, this));
}
bool intersects (const hb_set_t *glyphs) const
{
return
+ hb_iter (ligature)
| hb_map (hb_add (this))
| hb_map ([glyphs] (const Ligature<Types> &_) { return _.intersects (glyphs); })
| hb_any
;
}
bool intersects_lig_glyph (const hb_set_t *glyphs) const
{
return
+ hb_iter (ligature)
| hb_map (hb_add (this))
| hb_map ([glyphs] (const Ligature<Types> &_) {
return _.intersects_lig_glyph (glyphs) && _.intersects (glyphs);
})
| hb_any
;
}
void closure (hb_closure_context_t *c) const
{
+ hb_iter (ligature)
| hb_map (hb_add (this))
| hb_apply ([c] (const Ligature<Types> &_) { _.closure (c); })
;
}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
+ hb_iter (ligature)
| hb_map (hb_add (this))
| hb_apply ([c] (const Ligature<Types> &_) { _.collect_glyphs (c); })
;
}
bool would_apply (hb_would_apply_context_t *c) const
{
return
+ hb_iter (ligature)
| hb_map (hb_add (this))
| hb_map ([c] (const Ligature<Types> &_) { return _.would_apply (c); })
| hb_any
;
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int num_ligs = ligature.len;
#ifndef HB_NO_OT_RULESETS_FAST_PATH
if (HB_OPTIMIZE_SIZE_VAL || num_ligs <= 4)
#endif
{
slow:
for (unsigned int i = 0; i < num_ligs; i++)
{
const auto &lig = this+ligature.arrayZ[i];
if (lig.apply (c)) return_trace (true);
}
return_trace (false);
}
/* This version is optimized for speed by matching the first component
* of the ligature here, instead of calling into the ligation code.
*
* This is replicated in ChainRuleSet and RuleSet. */
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset (c->buffer->idx);
skippy_iter.set_match_func (match_always, nullptr);
skippy_iter.set_glyph_data ((HBUINT16 *) nullptr);
unsigned unsafe_to;
hb_codepoint_t first = (unsigned) -1;
bool matched = skippy_iter.next (&unsafe_to);
if (likely (matched))
{
first = c->buffer->info[skippy_iter.idx].codepoint;
unsafe_to = skippy_iter.idx + 1;
if (skippy_iter.may_skip (c->buffer->info[skippy_iter.idx]))
{
/* Can't use the fast path if eg. the next char is a default-ignorable
* or other skippable. */
goto slow;
}
}
else
goto slow;
bool unsafe_to_concat = false;
for (unsigned int i = 0; i < num_ligs; i++)
{
const auto &lig = this+ligature.arrayZ[i];
if (unlikely (lig.component.lenP1 <= 1) ||
lig.component.arrayZ[0] == first)
{
if (lig.apply (c))
{
if (unsafe_to_concat)
c->buffer->unsafe_to_concat (c->buffer->idx, unsafe_to);
return_trace (true);
}
}
else if (likely (lig.component.lenP1 > 1))
unsafe_to_concat = true;
}
if (likely (unsafe_to_concat))
c->buffer->unsafe_to_concat (c->buffer->idx, unsafe_to);
return_trace (false);
}
bool serialize (hb_serialize_context_t *c,
hb_array_t<const HBGlyphID16> ligatures,
hb_array_t<const unsigned int> component_count_list,
hb_array_t<const HBGlyphID16> &component_list /* Starting from second for each ligature */)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!ligature.serialize (c, ligatures.length))) return_trace (false);
for (unsigned int i = 0; i < ligatures.length; i++)
{
unsigned int component_count = (unsigned) hb_max ((int) component_count_list[i] - 1, 0);
if (unlikely (!ligature[i].serialize_serialize (c,
ligatures[i],
component_list.sub_array (0, component_count))))
return_trace (false);
component_list += component_count;
}
return_trace (true);
}
bool subset (hb_subset_context_t *c, unsigned coverage_idx) const
{
TRACE_SUBSET (this);
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
+ hb_iter (ligature)
| hb_filter (subset_offset_array (c, out->ligature, this, coverage_idx))
| hb_drain
;
if (bool (out->ligature))
// Ensure Coverage table is always packed after this.
c->serializer->add_virtual_link (coverage_idx);
return_trace (bool (out->ligature));
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_LIGATURESET_HH */
@@ -0,0 +1,71 @@
#ifndef OT_LAYOUT_GSUB_LIGATURESUBST_HH
#define OT_LAYOUT_GSUB_LIGATURESUBST_HH
#include "Common.hh"
#include "LigatureSubstFormat1.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct LigatureSubst
{
protected:
union {
HBUINT16 format; /* Format identifier */
LigatureSubstFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
LigatureSubstFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
/* TODO This function is only used by small GIDs, and not updated to 24bit GIDs. Should
* be done by using iterators. While at it perhaps using iterator of arrays of hb_codepoint_t
* instead. */
bool serialize (hb_serialize_context_t *c,
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
hb_array_t<const HBGlyphID16> ligatures_list,
hb_array_t<const unsigned int> component_count_list,
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (u.format))) return_trace (false);
unsigned int format = 1;
u.format = format;
switch (u.format) {
case 1: return_trace (u.format1.serialize (c,
first_glyphs,
ligature_per_first_glyph_count_list,
ligatures_list,
component_count_list,
component_list));
default:return_trace (false);
}
}
/* TODO subset() should choose format. */
};
}
}
}
#endif /* OT_LAYOUT_GSUB_LIGATURESUBST_HH */
@@ -0,0 +1,166 @@
#ifndef OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH
#define OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH
#include "Common.hh"
#include "LigatureSet.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct LigatureSubstFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of Substitution table */
Array16Of<typename Types::template OffsetTo<LigatureSet<Types>>>
ligatureSet; /* Array LigatureSet tables
* ordered by Coverage Index */
public:
DEFINE_SIZE_ARRAY (4 + Types::size, ligatureSet);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (coverage.sanitize (c, this) && ligatureSet.sanitize (c, this));
}
bool intersects (const hb_set_t *glyphs) const
{
return
+ hb_zip (this+coverage, ligatureSet)
| hb_filter (*glyphs, hb_first)
| hb_map (hb_second)
| hb_map ([this, glyphs] (const typename Types::template OffsetTo<LigatureSet<Types>> &_)
{ return (this+_).intersects (glyphs); })
| hb_any
;
}
bool may_have_non_1to1 () const
{ return true; }
void closure (hb_closure_context_t *c) const
{
+ hb_zip (this+coverage, ligatureSet)
| hb_filter (c->parent_active_glyphs (), hb_first)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const LigatureSet<Types> &_) { _.closure (c); })
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
+ hb_zip (this+coverage, ligatureSet)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const LigatureSet<Types> &_) { _.collect_glyphs (c); })
;
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{
unsigned int index = (this+coverage).get_coverage (c->glyphs[0]);
if (likely (index == NOT_COVERED)) return false;
const auto &lig_set = this+ligatureSet[index];
return lig_set.would_apply (c);
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage).get_coverage (c->buffer->cur ().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
const auto &lig_set = this+ligatureSet[index];
return_trace (lig_set.apply (c));
}
bool serialize (hb_serialize_context_t *c,
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
hb_array_t<const HBGlyphID16> ligatures_list,
hb_array_t<const unsigned int> component_count_list,
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!ligatureSet.serialize (c, first_glyphs.length))) return_trace (false);
for (unsigned int i = 0; i < first_glyphs.length; i++)
{
unsigned int ligature_count = ligature_per_first_glyph_count_list[i];
if (unlikely (!ligatureSet[i]
.serialize_serialize (c,
ligatures_list.sub_array (0, ligature_count),
component_count_list.sub_array (0, ligature_count),
component_list))) return_trace (false);
ligatures_list += ligature_count;
component_count_list += ligature_count;
}
return_trace (coverage.serialize_serialize (c, first_glyphs));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
// Due to a bug in some older versions of windows 7 the Coverage table must be
// packed after the LigatureSet and Ligature tables, so serialize Coverage first
// which places it last in the packed order.
hb_set_t new_coverage;
+ hb_zip (this+coverage, hb_iter (ligatureSet) | hb_map (hb_add (this)))
| hb_filter (glyphset, hb_first)
| hb_filter ([&] (const LigatureSet<Types>& _) {
return _.intersects_lig_glyph (&glyphset);
}, hb_second)
| hb_map (hb_first)
| hb_sink (new_coverage);
if (!c->serializer->push<Coverage> ()
->serialize (c->serializer,
+ new_coverage.iter () | hb_map_retains_sorting (glyph_map)))
{
c->serializer->pop_discard ();
return_trace (false);
}
unsigned coverage_idx = c->serializer->pop_pack ();
c->serializer->add_link (out->coverage, coverage_idx);
+ hb_zip (this+coverage, ligatureSet)
| hb_filter (new_coverage, hb_first)
| hb_map (hb_second)
// to ensure that the repacker always orders the coverage table after the LigatureSet
// and LigatureSubtable's they will be linked to the Coverage table via a virtual link
// the coverage table object idx is passed down to facilitate this.
| hb_apply (subset_offset_array (c, out->ligatureSet, this, coverage_idx))
;
return_trace (bool (new_coverage));
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH */
@@ -0,0 +1,62 @@
#ifndef OT_LAYOUT_GSUB_MULTIPLESUBST_HH
#define OT_LAYOUT_GSUB_MULTIPLESUBST_HH
#include "Common.hh"
#include "MultipleSubstFormat1.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct MultipleSubst
{
protected:
union {
HBUINT16 format; /* Format identifier */
MultipleSubstFormat1_2<SmallTypes> format1;
#ifndef HB_NO_BEYOND_64K
MultipleSubstFormat1_2<MediumTypes> format2;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
template<typename Iterator,
hb_requires (hb_is_sorted_iterator (Iterator))>
bool serialize (hb_serialize_context_t *c,
Iterator it)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (u.format))) return_trace (false);
unsigned int format = 1;
u.format = format;
switch (u.format) {
case 1: return_trace (u.format1.serialize (c, it));
default:return_trace (false);
}
}
/* TODO subset() should choose format. */
};
}
}
}
#endif /* OT_LAYOUT_GSUB_MULTIPLESUBST_HH */
@@ -0,0 +1,130 @@
#ifndef OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH
#define OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH
#include "Common.hh"
#include "Sequence.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct MultipleSubstFormat1_2
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of Substitution table */
Array16Of<typename Types::template OffsetTo<Sequence<Types>>>
sequence; /* Array of Sequence tables
* ordered by Coverage Index */
public:
DEFINE_SIZE_ARRAY (4 + Types::size, sequence);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (coverage.sanitize (c, this) && sequence.sanitize (c, this));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
bool may_have_non_1to1 () const
{ return true; }
void closure (hb_closure_context_t *c) const
{
+ hb_zip (this+coverage, sequence)
| hb_filter (c->parent_active_glyphs (), hb_first)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const Sequence<Types> &_) { _.closure (c); })
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
+ hb_zip (this+coverage, sequence)
| hb_map (hb_second)
| hb_map (hb_add (this))
| hb_apply ([c] (const Sequence<Types> &_) { _.collect_glyphs (c); })
;
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
return_trace ((this+sequence[index]).apply (c));
}
template<typename Iterator,
hb_requires (hb_is_sorted_iterator (Iterator))>
bool serialize (hb_serialize_context_t *c,
Iterator it)
{
TRACE_SERIALIZE (this);
auto sequences =
+ it
| hb_map (hb_second)
;
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!sequence.serialize (c, sequences.length))) return_trace (false);
for (auto& pair : hb_zip (sequences, sequence))
{
if (unlikely (!pair.second
.serialize_serialize (c, pair.first)))
return_trace (false);
}
return_trace (coverage.serialize_serialize (c, glyphs));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
+ hb_zip (this+coverage, sequence)
| hb_filter (glyphset, hb_first)
| hb_filter (subset_offset_array (c, out->sequence, this), hb_second)
| hb_map (hb_first)
| hb_map (glyph_map)
| hb_sink (new_coverage)
;
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
return_trace (bool (new_coverage));
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH */
@@ -0,0 +1,36 @@
#ifndef OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH
#define OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH
#include "Common.hh"
#include "ReverseChainSingleSubstFormat1.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct ReverseChainSingleSubst
{
protected:
union {
HBUINT16 format; /* Format identifier */
ReverseChainSingleSubstFormat1 format1;
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
};
}
}
}
#endif /* HB_OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH */
@@ -0,0 +1,245 @@
#ifndef OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH
#define OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct ReverseChainSingleSubstFormat1
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
Offset16To<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of table */
Array16OfOffset16To<Coverage>
backtrack; /* Array of coverage tables
* in backtracking sequence, in glyph
* sequence order */
Array16OfOffset16To<Coverage>
lookaheadX; /* Array of coverage tables
* in lookahead sequence, in glyph
* sequence order */
Array16Of<HBGlyphID16>
substituteX; /* Array of substitute
* GlyphIDs--ordered by Coverage Index */
public:
DEFINE_SIZE_MIN (10);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
if (!(coverage.sanitize (c, this) && backtrack.sanitize (c, this)))
return_trace (false);
hb_barrier ();
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
if (!lookahead.sanitize (c, this))
return_trace (false);
hb_barrier ();
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
return_trace (substitute.sanitize (c));
}
bool intersects (const hb_set_t *glyphs) const
{
if (!(this+coverage).intersects (glyphs))
return false;
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
unsigned int count;
count = backtrack.len;
for (unsigned int i = 0; i < count; i++)
if (!(this+backtrack[i]).intersects (glyphs))
return false;
count = lookahead.len;
for (unsigned int i = 0; i < count; i++)
if (!(this+lookahead[i]).intersects (glyphs))
return false;
return true;
}
bool may_have_non_1to1 () const
{ return false; }
void closure (hb_closure_context_t *c) const
{
if (!intersects (c->glyphs)) return;
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
+ hb_zip (this+coverage, substitute)
| hb_filter (c->parent_active_glyphs (), hb_first)
| hb_map (hb_second)
| hb_sink (c->output)
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
unsigned int count;
count = backtrack.len;
for (unsigned int i = 0; i < count; i++)
if (unlikely (!(this+backtrack[i]).collect_coverage (c->before))) return;
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
count = lookahead.len;
for (unsigned int i = 0; i < count; i++)
if (unlikely (!(this+lookahead[i]).collect_coverage (c->after))) return;
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
count = substitute.len;
c->output->add_array (substitute.arrayZ, substitute.len);
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage).get_coverage (c->buffer->cur ().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
if (unlikely (c->nesting_level_left != HB_MAX_NESTING_LEVEL))
return_trace (false); /* No chaining to this type */
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
if (unlikely (index >= substitute.len)) return_trace (false);
unsigned int start_index = 0, end_index = 0;
if (match_backtrack (c,
backtrack.len, (HBUINT16 *) backtrack.arrayZ,
match_coverage, this,
&start_index) &&
match_lookahead (c,
lookahead.len, (HBUINT16 *) lookahead.arrayZ,
match_coverage, this,
c->buffer->idx + 1, &end_index))
{
c->buffer->unsafe_to_break_from_outbuffer (start_index, end_index);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replacing glyph at %u (reverse chaining substitution)",
c->buffer->idx);
}
c->replace_glyph_inplace (substitute[index]);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (reverse chaining substitution)",
c->buffer->idx);
}
/* Note: We DON'T decrease buffer->idx. The main loop does it
* for us. This is useful for preventing surprises if someone
* calls us through a Context lookup. */
return_trace (true);
}
else
{
c->buffer->unsafe_to_concat_from_outbuffer (start_index, end_index);
return_trace (false);
}
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
bool serialize_coverage_offset_array (hb_subset_context_t *c, Iterator it) const
{
TRACE_SERIALIZE (this);
auto *out = c->serializer->start_embed<Array16OfOffset16To<Coverage>> ();
if (unlikely (!c->serializer->allocate_size<HBUINT16> (HBUINT16::static_size)))
return_trace (false);
for (auto& offset : it) {
auto *o = out->serialize_append (c->serializer);
if (unlikely (!o) || !o->serialize_subset (c, offset, this))
return_trace (false);
}
return_trace (true);
}
template<typename Iterator, typename BacktrackIterator, typename LookaheadIterator,
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_pair_t)),
hb_requires (hb_is_iterator (BacktrackIterator)),
hb_requires (hb_is_iterator (LookaheadIterator))>
bool serialize (hb_subset_context_t *c,
Iterator coverage_subst_iter,
BacktrackIterator backtrack_iter,
LookaheadIterator lookahead_iter) const
{
TRACE_SERIALIZE (this);
auto *out = c->serializer->start_embed (this);
if (unlikely (!c->serializer->embed (this->format))) return_trace (false);
if (unlikely (!c->serializer->embed (this->coverage))) return_trace (false);
if (!serialize_coverage_offset_array (c, backtrack_iter)) return_trace (false);
if (!serialize_coverage_offset_array (c, lookahead_iter)) return_trace (false);
auto *substitute_out = c->serializer->start_embed<Array16Of<HBGlyphID16>> ();
auto substitutes =
+ coverage_subst_iter
| hb_map (hb_second)
;
auto glyphs =
+ coverage_subst_iter
| hb_map_retains_sorting (hb_first)
;
if (unlikely (! c->serializer->check_success (substitute_out->serialize (c->serializer, substitutes))))
return_trace (false);
if (unlikely (!out->coverage.serialize_serialize (c->serializer, glyphs)))
return_trace (false);
return_trace (true);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
auto it =
+ hb_zip (this+coverage, substitute)
| hb_filter (glyphset, hb_first)
| hb_filter (glyphset, hb_second)
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const HBGlyphID16 &> p) -> hb_codepoint_pair_t
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
;
return_trace (bool (it) && serialize (c, it, backtrack.iter (), lookahead.iter ()));
}
};
}
}
}
#endif /* HB_OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH */
@@ -0,0 +1,165 @@
#ifndef OT_LAYOUT_GSUB_SEQUENCE_HH
#define OT_LAYOUT_GSUB_SEQUENCE_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct Sequence
{
protected:
Array16Of<typename Types::HBGlyphID>
substitute; /* String of GlyphIDs to substitute */
public:
DEFINE_SIZE_ARRAY (2, substitute);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (substitute.sanitize (c));
}
bool intersects (const hb_set_t *glyphs) const
{ return hb_all (substitute, glyphs); }
void closure (hb_closure_context_t *c) const
{ c->output->add_array (substitute.arrayZ, substitute.len); }
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{ c->output->add_array (substitute.arrayZ, substitute.len); }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int count = substitute.len;
/* Special-case to make it in-place and not consider this
* as a "multiplied" substitution. */
if (unlikely (count == 1))
{
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"replacing glyph at %u (multiple substitution)",
c->buffer->idx);
}
c->replace_glyph (substitute.arrayZ[0]);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (multiple substitution)",
c->buffer->idx - 1u);
}
return_trace (true);
}
/* Spec disallows this, but Uniscribe allows it.
* https://github.com/harfbuzz/harfbuzz/issues/253 */
else if (unlikely (count == 0))
{
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"deleting glyph at %u (multiple substitution)",
c->buffer->idx);
}
c->buffer->delete_glyph ();
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"deleted glyph at %u (multiple substitution)",
c->buffer->idx);
}
return_trace (true);
}
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"multiplying glyph at %u",
c->buffer->idx);
}
unsigned int klass = _hb_glyph_info_is_ligature (&c->buffer->cur()) ?
HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH : 0;
unsigned lig_id = _hb_glyph_info_get_lig_id (&c->buffer->cur());
for (unsigned int i = 0; i < count; i++)
{
/* If is attached to a ligature, don't disturb that.
* https://github.com/harfbuzz/harfbuzz/issues/3069 */
if (!lig_id)
_hb_glyph_info_set_lig_props_for_component (&c->buffer->cur(), i);
c->output_glyph_for_component (substitute.arrayZ[i], klass);
}
c->buffer->skip_glyph ();
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
char buf[HB_MAX_CONTEXT_LENGTH * 16] = {0};
char *p = buf;
for (unsigned i = c->buffer->idx - count; i < c->buffer->idx; i++)
{
if (buf < p)
*p++ = ',';
snprintf (p, sizeof(buf) - (p - buf), "%u", i);
p += strlen(p);
}
c->buffer->message (c->font,
"multiplied glyphs at %s",
buf);
}
return_trace (true);
}
template <typename Iterator,
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c,
Iterator subst)
{
TRACE_SERIALIZE (this);
return_trace (substitute.serialize (c, subst));
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
if (!intersects (&glyphset)) return_trace (false);
auto it =
+ hb_iter (substitute)
| hb_map (glyph_map)
;
auto *out = c->serializer->start_embed (*this);
return_trace (out->serialize (c->serializer, it));
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_SEQUENCE_HH */
@@ -0,0 +1,103 @@
#ifndef OT_LAYOUT_GSUB_SINGLESUBST_HH
#define OT_LAYOUT_GSUB_SINGLESUBST_HH
#include "Common.hh"
#include "SingleSubstFormat1.hh"
#include "SingleSubstFormat2.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct SingleSubst
{
protected:
union {
HBUINT16 format; /* Format identifier */
SingleSubstFormat1_3<SmallTypes> format1;
SingleSubstFormat2_4<SmallTypes> format2;
#ifndef HB_NO_BEYOND_64K
SingleSubstFormat1_3<MediumTypes> format3;
SingleSubstFormat2_4<MediumTypes> format4;
#endif
} u;
public:
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
}
template<typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator,
const hb_codepoint_pair_t))>
bool serialize (hb_serialize_context_t *c,
Iterator glyphs)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (u.format))) return_trace (false);
unsigned format = 2;
unsigned delta = 0;
if (glyphs)
{
format = 1;
hb_codepoint_t mask = 0xFFFFu;
#ifndef HB_NO_BEYOND_64K
if (+ glyphs
| hb_map_retains_sorting (hb_second)
| hb_filter ([] (hb_codepoint_t gid) { return gid > 0xFFFFu; }))
{
format += 2;
mask = 0xFFFFFFu;
}
#endif
auto get_delta = [=] (hb_codepoint_pair_t _)
{ return (unsigned) (_.second - _.first) & mask; };
delta = get_delta (*glyphs);
if (!hb_all (++(+glyphs), delta, get_delta)) format += 1;
}
u.format = format;
switch (u.format) {
case 1: return_trace (u.format1.serialize (c,
+ glyphs
| hb_map_retains_sorting (hb_first),
delta));
case 2: return_trace (u.format2.serialize (c, glyphs));
#ifndef HB_NO_BEYOND_64K
case 3: return_trace (u.format3.serialize (c,
+ glyphs
| hb_map_retains_sorting (hb_first),
delta));
case 4: return_trace (u.format4.serialize (c, glyphs));
#endif
default:return_trace (false);
}
}
};
template<typename Iterator>
static void
SingleSubst_serialize (hb_serialize_context_t *c,
Iterator it)
{ c->start_embed<SingleSubst> ()->serialize (c, it); }
}
}
}
#endif /* OT_LAYOUT_GSUB_SINGLESUBST_HH */
@@ -0,0 +1,204 @@
#ifndef OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH
#define OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct SingleSubstFormat1_3
{
protected:
HBUINT16 format; /* Format identifier--format = 1 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of Substitution table */
typename Types::HBUINT
deltaGlyphID; /* Add to original GlyphID to get
* substitute GlyphID, modulo 0x10000 */
public:
DEFINE_SIZE_STATIC (2 + 2 * Types::size);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this) &&
coverage.sanitize (c, this) &&
/* The coverage table may use a range to represent a set
* of glyphs, which means a small number of bytes can
* generate a large glyph set. Manually modify the
* sanitizer max ops to take this into account.
*
* Note: This check *must* be right after coverage sanitize. */
c->check_ops ((this + coverage).get_population () >> 1));
}
hb_codepoint_t get_mask () const
{ return (1 << (8 * Types::size)) - 1; }
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
bool may_have_non_1to1 () const
{ return false; }
void closure (hb_closure_context_t *c) const
{
hb_codepoint_t d = deltaGlyphID;
hb_codepoint_t mask = get_mask ();
/* Help fuzzer avoid this function as much. */
unsigned pop = (this+coverage).get_population ();
if (pop >= mask)
return;
hb_set_t intersection;
(this+coverage).intersect_set (c->parent_active_glyphs (), intersection);
/* In degenerate fuzzer-found fonts, but not real fonts,
* this table can keep adding new glyphs in each round of closure.
* Refuse to close-over, if it maps glyph range to overlapping range. */
hb_codepoint_t min_before = intersection.get_min ();
hb_codepoint_t max_before = intersection.get_max ();
hb_codepoint_t min_after = (min_before + d) & mask;
hb_codepoint_t max_after = (max_before + d) & mask;
if (intersection.get_population () == max_before - min_before + 1 &&
((min_before <= min_after && min_after <= max_before) ||
(min_before <= max_after && max_after <= max_before)))
return;
+ hb_iter (intersection)
| hb_map ([d, mask] (hb_codepoint_t g) { return (g + d) & mask; })
| hb_sink (c->output)
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
hb_codepoint_t d = deltaGlyphID;
hb_codepoint_t mask = get_mask ();
+ hb_iter (this+coverage)
| hb_map ([d, mask] (hb_codepoint_t g) { return (g + d) & mask; })
| hb_sink (c->output)
;
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
unsigned
get_glyph_alternates (hb_codepoint_t glyph_id,
unsigned start_offset,
unsigned *alternate_count /* IN/OUT. May be NULL. */,
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
{
unsigned int index = (this+coverage).get_coverage (glyph_id);
if (likely (index == NOT_COVERED))
{
if (alternate_count)
*alternate_count = 0;
return 0;
}
if (alternate_count && *alternate_count)
{
hb_codepoint_t d = deltaGlyphID;
hb_codepoint_t mask = get_mask ();
glyph_id = (glyph_id + d) & mask;
*alternate_glyphs = glyph_id;
*alternate_count = 1;
}
return 1;
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
hb_codepoint_t glyph_id = c->buffer->cur().codepoint;
unsigned int index = (this+coverage).get_coverage (glyph_id);
if (likely (index == NOT_COVERED)) return_trace (false);
hb_codepoint_t d = deltaGlyphID;
hb_codepoint_t mask = get_mask ();
glyph_id = (glyph_id + d) & mask;
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"replacing glyph at %u (single substitution)",
c->buffer->idx);
}
c->replace_glyph (glyph_id);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (single substitution)",
c->buffer->idx - 1u);
}
return_trace (true);
}
template<typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
bool serialize (hb_serialize_context_t *c,
Iterator glyphs,
unsigned delta)
{
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!coverage.serialize_serialize (c, glyphs))) return_trace (false);
c->check_assign (deltaGlyphID, delta, HB_SERIALIZE_ERROR_INT_OVERFLOW);
return_trace (true);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
hb_codepoint_t d = deltaGlyphID;
hb_codepoint_t mask = get_mask ();
hb_set_t intersection;
(this+coverage).intersect_set (glyphset, intersection);
auto it =
+ hb_iter (intersection)
| hb_map_retains_sorting ([d, mask] (hb_codepoint_t g) {
return hb_codepoint_pair_t (g,
(g + d) & mask); })
| hb_filter (glyphset, hb_second)
| hb_map_retains_sorting ([&] (hb_codepoint_pair_t p) -> hb_codepoint_pair_t
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
;
bool ret = bool (it);
SingleSubst_serialize (c->serializer, it);
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH */
@@ -0,0 +1,176 @@
#ifndef OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH
#define OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH
#include "Common.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
template <typename Types>
struct SingleSubstFormat2_4
{
protected:
HBUINT16 format; /* Format identifier--format = 2 */
typename Types::template OffsetTo<Coverage>
coverage; /* Offset to Coverage table--from
* beginning of Substitution table */
Array16Of<typename Types::HBGlyphID>
substitute; /* Array of substitute
* GlyphIDs--ordered by Coverage Index */
public:
DEFINE_SIZE_ARRAY (4 + Types::size, substitute);
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (coverage.sanitize (c, this) && substitute.sanitize (c));
}
bool intersects (const hb_set_t *glyphs) const
{ return (this+coverage).intersects (glyphs); }
bool may_have_non_1to1 () const
{ return false; }
void closure (hb_closure_context_t *c) const
{
auto &cov = this+coverage;
auto &glyph_set = c->parent_active_glyphs ();
if (substitute.len > glyph_set.get_population () * 4)
{
for (auto g : glyph_set)
{
unsigned i = cov.get_coverage (g);
if (i == NOT_COVERED || i >= substitute.len)
continue;
c->output->add (substitute.arrayZ[i]);
}
return;
}
+ hb_zip (cov, substitute)
| hb_filter (glyph_set, hb_first)
| hb_map (hb_second)
| hb_sink (c->output)
;
}
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
+ hb_zip (this+coverage, substitute)
| hb_map (hb_second)
| hb_sink (c->output)
;
}
const Coverage &get_coverage () const { return this+coverage; }
bool would_apply (hb_would_apply_context_t *c) const
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
unsigned
get_glyph_alternates (hb_codepoint_t glyph_id,
unsigned start_offset,
unsigned *alternate_count /* IN/OUT. May be NULL. */,
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
{
unsigned int index = (this+coverage).get_coverage (glyph_id);
if (likely (index == NOT_COVERED))
{
if (alternate_count)
*alternate_count = 0;
return 0;
}
if (alternate_count && *alternate_count)
{
glyph_id = substitute[index];
*alternate_glyphs = glyph_id;
*alternate_count = 1;
}
return 1;
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return_trace (false);
if (unlikely (index >= substitute.len)) return_trace (false);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->sync_so_far ();
c->buffer->message (c->font,
"replacing glyph at %u (single substitution)",
c->buffer->idx);
}
c->replace_glyph (substitute[index]);
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
{
c->buffer->message (c->font,
"replaced glyph at %u (single substitution)",
c->buffer->idx - 1u);
}
return_trace (true);
}
template<typename Iterator,
hb_requires (hb_is_sorted_source_of (Iterator,
hb_codepoint_pair_t))>
bool serialize (hb_serialize_context_t *c,
Iterator it)
{
TRACE_SERIALIZE (this);
auto substitutes =
+ it
| hb_map (hb_second)
;
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
if (unlikely (!c->extend_min (this))) return_trace (false);
if (unlikely (!substitute.serialize (c, substitutes))) return_trace (false);
if (unlikely (!coverage.serialize_serialize (c, glyphs))) return_trace (false);
return_trace (true);
}
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
auto it =
+ hb_zip (this+coverage, substitute)
| hb_filter (glyphset, hb_first)
| hb_filter (glyphset, hb_second)
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const typename Types::HBGlyphID &> p) -> hb_codepoint_pair_t
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
;
bool ret = bool (it);
SingleSubst_serialize (c->serializer, it);
return_trace (ret);
}
};
}
}
}
#endif /* OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH */
@@ -0,0 +1,220 @@
#ifndef OT_LAYOUT_GSUB_SUBSTLOOKUP_HH
#define OT_LAYOUT_GSUB_SUBSTLOOKUP_HH
#include "Common.hh"
#include "SubstLookupSubTable.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct SubstLookup : Lookup
{
using SubTable = SubstLookupSubTable;
bool sanitize (hb_sanitize_context_t *c) const
{ return Lookup::sanitize<SubTable> (c); }
const SubTable& get_subtable (unsigned int i) const
{ return Lookup::get_subtable<SubTable> (i); }
static inline bool lookup_type_is_reverse (unsigned int lookup_type)
{ return lookup_type == SubTable::ReverseChainSingle; }
bool is_reverse () const
{
unsigned int type = get_type ();
if (unlikely (type == SubTable::Extension))
return get_subtable (0).u.extension.is_reverse ();
return lookup_type_is_reverse (type);
}
bool may_have_non_1to1 () const
{
hb_have_non_1to1_context_t c;
return dispatch (&c);
}
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
return_trace (dispatch (c));
}
bool intersects (const hb_set_t *glyphs) const
{
hb_intersects_context_t c (glyphs);
return dispatch (&c);
}
hb_closure_context_t::return_t closure (hb_closure_context_t *c, unsigned int this_index) const
{
if (!c->should_visit_lookup (this_index))
return hb_closure_context_t::default_return_value ();
c->set_recurse_func (dispatch_closure_recurse_func);
hb_closure_context_t::return_t ret = dispatch (c);
c->flush ();
return ret;
}
hb_closure_lookups_context_t::return_t closure_lookups (hb_closure_lookups_context_t *c, unsigned this_index) const
{
if (c->is_lookup_visited (this_index))
return hb_closure_lookups_context_t::default_return_value ();
c->set_lookup_visited (this_index);
if (!intersects (c->glyphs))
{
c->set_lookup_inactive (this_index);
return hb_closure_lookups_context_t::default_return_value ();
}
hb_closure_lookups_context_t::return_t ret = dispatch (c);
return ret;
}
hb_collect_glyphs_context_t::return_t collect_glyphs (hb_collect_glyphs_context_t *c) const
{
c->set_recurse_func (dispatch_recurse_func<hb_collect_glyphs_context_t>);
return dispatch (c);
}
template <typename set_t>
void collect_coverage (set_t *glyphs) const
{
hb_collect_coverage_context_t<set_t> c (glyphs);
dispatch (&c);
}
bool would_apply (hb_would_apply_context_t *c,
const hb_ot_layout_lookup_accelerator_t *accel) const
{
if (unlikely (!c->len)) return false;
if (!accel->may_have (c->glyphs[0])) return false;
return dispatch (c);
}
template<typename Glyphs, typename Substitutes,
hb_requires (hb_is_sorted_source_of (Glyphs,
const hb_codepoint_t) &&
hb_is_source_of (Substitutes,
const hb_codepoint_t))>
bool serialize_single (hb_serialize_context_t *c,
uint32_t lookup_props,
Glyphs glyphs,
Substitutes substitutes)
{
TRACE_SERIALIZE (this);
if (unlikely (!Lookup::serialize (c, SubTable::Single, lookup_props, 1))) return_trace (false);
if (c->push<SubTable> ()->u.single.serialize (c, hb_zip (glyphs, substitutes)))
{
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
return_trace (true);
}
c->pop_discard ();
return_trace (false);
}
template<typename Iterator,
hb_requires (hb_is_sorted_iterator (Iterator))>
bool serialize (hb_serialize_context_t *c,
uint32_t lookup_props,
Iterator it)
{
TRACE_SERIALIZE (this);
if (unlikely (!Lookup::serialize (c, SubTable::Multiple, lookup_props, 1))) return_trace (false);
if (c->push<SubTable> ()->u.multiple.
serialize (c, it))
{
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
return_trace (true);
}
c->pop_discard ();
return_trace (false);
}
bool serialize_alternate (hb_serialize_context_t *c,
uint32_t lookup_props,
hb_sorted_array_t<const HBGlyphID16> glyphs,
hb_array_t<const unsigned int> alternate_len_list,
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
{
TRACE_SERIALIZE (this);
if (unlikely (!Lookup::serialize (c, SubTable::Alternate, lookup_props, 1))) return_trace (false);
if (c->push<SubTable> ()->u.alternate.
serialize (c,
glyphs,
alternate_len_list,
alternate_glyphs_list))
{
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
return_trace (true);
}
c->pop_discard ();
return_trace (false);
}
bool serialize_ligature (hb_serialize_context_t *c,
uint32_t lookup_props,
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
hb_array_t<const HBGlyphID16> ligatures_list,
hb_array_t<const unsigned int> component_count_list,
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
{
TRACE_SERIALIZE (this);
if (unlikely (!Lookup::serialize (c, SubTable::Ligature, lookup_props, 1))) return_trace (false);
if (c->push<SubTable> ()->u.ligature.
serialize (c,
first_glyphs,
ligature_per_first_glyph_count_list,
ligatures_list,
component_count_list,
component_list))
{
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
return_trace (true);
}
c->pop_discard ();
return_trace (false);
}
template <typename context_t>
static inline typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
static inline typename hb_closure_context_t::return_t closure_glyphs_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index);
static inline hb_closure_context_t::return_t dispatch_closure_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index)
{
if (!c->should_visit_lookup (lookup_index))
return hb_empty_t ();
hb_closure_context_t::return_t ret = closure_glyphs_recurse_func (c, lookup_index, covered_seq_indices, seq_index, end_index);
/* While in theory we should flush here, it will cause timeouts because a recursive
* lookup can keep growing the glyph set. Skip, and outer loop will retry up to
* HB_CLOSURE_MAX_STAGES time, which should be enough for every realistic font. */
//c->flush ();
return ret;
}
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
{ return Lookup::dispatch<SubTable> (c, std::forward<Ts> (ds)...); }
bool subset (hb_subset_context_t *c) const
{ return Lookup::subset<SubTable> (c); }
};
}
}
}
#endif /* OT_LAYOUT_GSUB_SUBSTLOOKUP_HH */
@@ -0,0 +1,77 @@
#ifndef OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH
#define OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH
#include "Common.hh"
#include "SingleSubst.hh"
#include "MultipleSubst.hh"
#include "AlternateSubst.hh"
#include "LigatureSubst.hh"
#include "ContextSubst.hh"
#include "ChainContextSubst.hh"
#include "ExtensionSubst.hh"
#include "ReverseChainSingleSubst.hh"
namespace OT {
namespace Layout {
namespace GSUB_impl {
struct SubstLookupSubTable
{
friend struct ::OT::Lookup;
friend struct SubstLookup;
protected:
union {
SingleSubst single;
MultipleSubst multiple;
AlternateSubst alternate;
LigatureSubst ligature;
ContextSubst context;
ChainContextSubst chainContext;
ExtensionSubst extension;
ReverseChainSingleSubst reverseChainContextSingle;
} u;
public:
DEFINE_SIZE_MIN (0);
enum Type {
Single = 1,
Multiple = 2,
Alternate = 3,
Ligature = 4,
Context = 5,
ChainContext = 6,
Extension = 7,
ReverseChainSingle = 8
};
template <typename context_t, typename ...Ts>
typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type, Ts&&... ds) const
{
TRACE_DISPATCH (this, lookup_type);
switch (lookup_type) {
case Single: return_trace (u.single.dispatch (c, std::forward<Ts> (ds)...));
case Multiple: return_trace (u.multiple.dispatch (c, std::forward<Ts> (ds)...));
case Alternate: return_trace (u.alternate.dispatch (c, std::forward<Ts> (ds)...));
case Ligature: return_trace (u.ligature.dispatch (c, std::forward<Ts> (ds)...));
case Context: return_trace (u.context.dispatch (c, std::forward<Ts> (ds)...));
case ChainContext: return_trace (u.chainContext.dispatch (c, std::forward<Ts> (ds)...));
case Extension: return_trace (u.extension.dispatch (c, std::forward<Ts> (ds)...));
case ReverseChainSingle: return_trace (u.reverseChainContextSingle.dispatch (c, std::forward<Ts> (ds)...));
default: return_trace (c->default_return_value ());
}
}
bool intersects (const hb_set_t *glyphs, unsigned int lookup_type) const
{
hb_intersects_context_t c (glyphs);
return dispatch (&c, lookup_type);
}
};
}
}
}
#endif /* HB_OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH */
@@ -0,0 +1,66 @@
/*
* Copyright © 2007,2008,2009 Red Hat, Inc.
* Copyright © 2010,2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod, Garret Rieger
*/
#ifndef OT_LAYOUT_TYPES_HH
#define OT_LAYOUT_TYPES_HH
namespace OT {
namespace Layout {
struct SmallTypes {
static constexpr unsigned size = 2;
using large_int = uint32_t;
using HBUINT = HBUINT16;
using HBGlyphID = HBGlyphID16;
using Offset = Offset16;
template <typename Type, typename BaseType=void, bool has_null=true>
using OffsetTo = OT::Offset16To<Type, BaseType, has_null>;
template <typename Type>
using ArrayOf = OT::Array16Of<Type>;
template <typename Type>
using SortedArrayOf = OT::SortedArray16Of<Type>;
};
struct MediumTypes {
static constexpr unsigned size = 3;
using large_int = uint64_t;
using HBUINT = HBUINT24;
using HBGlyphID = HBGlyphID24;
using Offset = Offset24;
template <typename Type, typename BaseType=void, bool has_null=true>
using OffsetTo = OT::Offset24To<Type, BaseType, has_null>;
template <typename Type>
using ArrayOf = OT::Array24Of<Type>;
template <typename Type>
using SortedArrayOf = OT::SortedArray24Of<Type>;
};
}
}
#endif /* OT_LAYOUT_TYPES_HH */
@@ -0,0 +1,346 @@
#include "VARC.hh"
#ifndef HB_NO_VAR_COMPOSITES
#include "../../../hb-draw.hh"
#include "../../../hb-geometry.hh"
#include "../../../hb-ot-layout-common.hh"
#include "../../../hb-ot-layout-gdef-table.hh"
namespace OT {
//namespace Var {
struct hb_transforming_pen_context_t
{
hb_transform_t transform;
hb_draw_funcs_t *dfuncs;
void *data;
hb_draw_state_t *st;
};
static void
hb_transforming_pen_move_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
void *data,
hb_draw_state_t *st,
float to_x, float to_y,
void *user_data HB_UNUSED)
{
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
c->transform.transform_point (to_x, to_y);
c->dfuncs->move_to (c->data, *c->st, to_x, to_y);
}
static void
hb_transforming_pen_line_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
void *data,
hb_draw_state_t *st,
float to_x, float to_y,
void *user_data HB_UNUSED)
{
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
c->transform.transform_point (to_x, to_y);
c->dfuncs->line_to (c->data, *c->st, to_x, to_y);
}
static void
hb_transforming_pen_quadratic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
void *data,
hb_draw_state_t *st,
float control_x, float control_y,
float to_x, float to_y,
void *user_data HB_UNUSED)
{
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
c->transform.transform_point (control_x, control_y);
c->transform.transform_point (to_x, to_y);
c->dfuncs->quadratic_to (c->data, *c->st, control_x, control_y, to_x, to_y);
}
static void
hb_transforming_pen_cubic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
void *data,
hb_draw_state_t *st,
float control1_x, float control1_y,
float control2_x, float control2_y,
float to_x, float to_y,
void *user_data HB_UNUSED)
{
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
c->transform.transform_point (control1_x, control1_y);
c->transform.transform_point (control2_x, control2_y);
c->transform.transform_point (to_x, to_y);
c->dfuncs->cubic_to (c->data, *c->st, control1_x, control1_y, control2_x, control2_y, to_x, to_y);
}
static void
hb_transforming_pen_close_path (hb_draw_funcs_t *dfuncs HB_UNUSED,
void *data,
hb_draw_state_t *st,
void *user_data HB_UNUSED)
{
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
c->dfuncs->close_path (c->data, *c->st);
}
static inline void free_static_transforming_pen_funcs ();
static struct hb_transforming_pen_funcs_lazy_loader_t : hb_draw_funcs_lazy_loader_t<hb_transforming_pen_funcs_lazy_loader_t>
{
static hb_draw_funcs_t *create ()
{
hb_draw_funcs_t *funcs = hb_draw_funcs_create ();
hb_draw_funcs_set_move_to_func (funcs, hb_transforming_pen_move_to, nullptr, nullptr);
hb_draw_funcs_set_line_to_func (funcs, hb_transforming_pen_line_to, nullptr, nullptr);
hb_draw_funcs_set_quadratic_to_func (funcs, hb_transforming_pen_quadratic_to, nullptr, nullptr);
hb_draw_funcs_set_cubic_to_func (funcs, hb_transforming_pen_cubic_to, nullptr, nullptr);
hb_draw_funcs_set_close_path_func (funcs, hb_transforming_pen_close_path, nullptr, nullptr);
hb_draw_funcs_make_immutable (funcs);
hb_atexit (free_static_transforming_pen_funcs);
return funcs;
}
} static_transforming_pen_funcs;
static inline
void free_static_transforming_pen_funcs ()
{
static_transforming_pen_funcs.free_instance ();
}
static hb_draw_funcs_t *
hb_transforming_pen_get_funcs ()
{
return static_transforming_pen_funcs.get_unconst ();
}
hb_ubytes_t
VarComponent::get_path_at (hb_font_t *font,
hb_codepoint_t parent_gid,
hb_draw_session_t &draw_session,
hb_array_t<const int> coords,
hb_ubytes_t total_record,
hb_set_t *visited,
signed *edges_left,
signed depth_left,
VarRegionList::cache_t *cache) const
{
const unsigned char *end = total_record.arrayZ + total_record.length;
const unsigned char *record = total_record.arrayZ;
auto &VARC = *font->face->table.VARC;
auto &varStore = &VARC+VARC.varStore;
auto instancer = MultiItemVarStoreInstancer(&varStore, nullptr, coords, cache);
#define READ_UINT32VAR(name) \
HB_STMT_START { \
if (unlikely (unsigned (end - record) < HBUINT32VAR::min_size)) return hb_ubytes_t (); \
hb_barrier (); \
auto &varint = * (const HBUINT32VAR *) record; \
unsigned size = varint.get_size (); \
if (unlikely (unsigned (end - record) < size)) return hb_ubytes_t (); \
name = (uint32_t) varint; \
record += size; \
} HB_STMT_END
uint32_t flags;
READ_UINT32VAR (flags);
// gid
hb_codepoint_t gid = 0;
if (flags & (unsigned) flags_t::GID_IS_24BIT)
{
if (unlikely (unsigned (end - record) < HBGlyphID24::static_size))
return hb_ubytes_t ();
hb_barrier ();
gid = * (const HBGlyphID24 *) record;
record += HBGlyphID24::static_size;
}
else
{
if (unlikely (unsigned (end - record) < HBGlyphID16::static_size))
return hb_ubytes_t ();
hb_barrier ();
gid = * (const HBGlyphID16 *) record;
record += HBGlyphID16::static_size;
}
// Condition
bool show = true;
if (flags & (unsigned) flags_t::HAVE_CONDITION)
{
unsigned conditionIndex;
READ_UINT32VAR (conditionIndex);
const auto &condition = (&VARC+VARC.conditionList)[conditionIndex];
show = condition.evaluate (coords.arrayZ, coords.length, &instancer);
}
// Axis values
hb_vector_t<unsigned> axisIndices;
hb_vector_t<float> axisValues;
if (flags & (unsigned) flags_t::HAVE_AXES)
{
unsigned axisIndicesIndex;
READ_UINT32VAR (axisIndicesIndex);
axisIndices = (&VARC+VARC.axisIndicesList)[axisIndicesIndex];
axisValues.resize (axisIndices.length);
const HBUINT8 *p = (const HBUINT8 *) record;
TupleValues::decompile (p, axisValues, (const HBUINT8 *) end);
record += (const unsigned char *) p - record;
}
// Apply variations if any
if (flags & (unsigned) flags_t::AXIS_VALUES_HAVE_VARIATION)
{
uint32_t axisValuesVarIdx;
READ_UINT32VAR (axisValuesVarIdx);
if (show && coords && !axisValues.in_error ())
varStore.get_delta (axisValuesVarIdx, coords, axisValues.as_array (), cache);
}
auto component_coords = coords;
/* Copying coords is expensive; so we have put an arbitrary
* limit on the max number of coords for now. */
if ((flags & (unsigned) flags_t::RESET_UNSPECIFIED_AXES) ||
coords.length > HB_VAR_COMPOSITE_MAX_AXES)
component_coords = hb_array<int> (font->coords, font->num_coords);
// Transform
uint32_t transformVarIdx = VarIdx::NO_VARIATION;
if (flags & (unsigned) flags_t::TRANSFORM_HAS_VARIATION)
READ_UINT32VAR (transformVarIdx);
#define PROCESS_TRANSFORM_COMPONENTS \
HB_STMT_START { \
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_X, translateX); \
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_Y, translateY); \
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_ROTATION, rotation); \
PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_X, scaleX); \
PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_Y, scaleY); \
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_X, skewX); \
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_Y, skewY); \
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_X, tCenterX); \
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_Y, tCenterY); \
} HB_STMT_END
hb_transform_decomposed_t transform;
// Read transform components
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
if (flags & (unsigned) flags_t::flag) \
{ \
static_assert (type::static_size == HBINT16::static_size, ""); \
if (unlikely (unsigned (end - record) < HBINT16::static_size)) \
return hb_ubytes_t (); \
hb_barrier (); \
transform.name = * (const HBINT16 *) record; \
record += HBINT16::static_size; \
}
PROCESS_TRANSFORM_COMPONENTS;
#undef PROCESS_TRANSFORM_COMPONENT
// Read reserved records
unsigned i = flags & (unsigned) flags_t::RESERVED_MASK;
while (i)
{
HB_UNUSED uint32_t discard;
READ_UINT32VAR (discard);
i &= i - 1;
}
/* Parsing is over now. */
if (show)
{
// Only use coord_setter if there's actually any axis overrides.
coord_setter_t coord_setter (axisIndices ? component_coords : hb_array<int> ());
// Go backwards, to reduce coord_setter vector reallocations.
for (unsigned i = axisIndices.length; i; i--)
coord_setter[axisIndices[i - 1]] = axisValues[i - 1];
if (axisIndices)
component_coords = coord_setter.get_coords ();
// Apply transform variations if any
if (transformVarIdx != VarIdx::NO_VARIATION && coords)
{
float transformValues[9];
unsigned numTransformValues = 0;
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
if (flags & (unsigned) flags_t::flag) \
transformValues[numTransformValues++] = transform.name;
PROCESS_TRANSFORM_COMPONENTS;
#undef PROCESS_TRANSFORM_COMPONENT
varStore.get_delta (transformVarIdx, coords, hb_array (transformValues, numTransformValues), cache);
numTransformValues = 0;
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
if (flags & (unsigned) flags_t::flag) \
transform.name = transformValues[numTransformValues++];
PROCESS_TRANSFORM_COMPONENTS;
#undef PROCESS_TRANSFORM_COMPONENT
}
// Divide them by their divisors
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
if (flags & (unsigned) flags_t::flag) \
{ \
HBINT16 int_v; \
int_v = roundf (transform.name); \
type typed_v = * (const type *) &int_v; \
float float_v = (float) typed_v; \
transform.name = float_v; \
}
PROCESS_TRANSFORM_COMPONENTS;
#undef PROCESS_TRANSFORM_COMPONENT
if (!(flags & (unsigned) flags_t::HAVE_SCALE_Y))
transform.scaleY = transform.scaleX;
// Scale the transform by the font's scale
float x_scale = font->x_multf;
float y_scale = font->y_multf;
transform.translateX *= x_scale;
transform.translateY *= y_scale;
transform.tCenterX *= x_scale;
transform.tCenterY *= y_scale;
// Build a transforming pen to apply the transform.
hb_draw_funcs_t *transformer_funcs = hb_transforming_pen_get_funcs ();
hb_transforming_pen_context_t context {transform.to_transform (),
draw_session.funcs,
draw_session.draw_data,
&draw_session.st};
hb_draw_session_t transformer_session {transformer_funcs, &context};
VARC.get_path_at (font, gid,
transformer_session, component_coords,
parent_gid,
visited, edges_left, depth_left - 1);
}
#undef PROCESS_TRANSFORM_COMPONENTS
#undef READ_UINT32VAR
return hb_ubytes_t (record, end - record);
}
//} // namespace Var
} // namespace OT
#endif
@@ -0,0 +1,193 @@
#ifndef OT_VAR_VARC_VARC_HH
#define OT_VAR_VARC_VARC_HH
#include "../../../hb-ot-layout-common.hh"
#include "../../../hb-ot-glyf-table.hh"
#include "../../../hb-ot-cff2-table.hh"
#include "../../../hb-ot-cff1-table.hh"
#include "coord-setter.hh"
namespace OT {
//namespace Var {
/*
* VARC -- Variable Composites
* https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md
*/
#ifndef HB_NO_VAR_COMPOSITES
struct VarComponent
{
enum class flags_t : uint32_t
{
RESET_UNSPECIFIED_AXES = 1u << 0,
HAVE_AXES = 1u << 1,
AXIS_VALUES_HAVE_VARIATION = 1u << 2,
TRANSFORM_HAS_VARIATION = 1u << 3,
HAVE_TRANSLATE_X = 1u << 4,
HAVE_TRANSLATE_Y = 1u << 5,
HAVE_ROTATION = 1u << 6,
HAVE_CONDITION = 1u << 7,
HAVE_SCALE_X = 1u << 8,
HAVE_SCALE_Y = 1u << 9,
HAVE_TCENTER_X = 1u << 10,
HAVE_TCENTER_Y = 1u << 11,
GID_IS_24BIT = 1u << 12,
HAVE_SKEW_X = 1u << 13,
HAVE_SKEW_Y = 1u << 14,
RESERVED_MASK = ~((1u << 15) - 1),
};
HB_INTERNAL hb_ubytes_t
get_path_at (hb_font_t *font,
hb_codepoint_t parent_gid,
hb_draw_session_t &draw_session,
hb_array_t<const int> coords,
hb_ubytes_t record,
hb_set_t *visited,
signed *edges_left,
signed depth_left,
VarRegionList::cache_t *cache = nullptr) const;
};
struct VarCompositeGlyph
{
static void
get_path_at (hb_font_t *font,
hb_codepoint_t glyph,
hb_draw_session_t &draw_session,
hb_array_t<const int> coords,
hb_ubytes_t record,
hb_set_t *visited,
signed *edges_left,
signed depth_left,
VarRegionList::cache_t *cache = nullptr)
{
while (record)
{
const VarComponent &comp = * (const VarComponent *) (record.arrayZ);
record = comp.get_path_at (font, glyph,
draw_session, coords,
record,
visited, edges_left, depth_left, cache);
}
}
};
HB_MARK_AS_FLAG_T (VarComponent::flags_t);
struct VARC
{
friend struct VarComponent;
static constexpr hb_tag_t tableTag = HB_TAG ('V', 'A', 'R', 'C');
bool
get_path_at (hb_font_t *font,
hb_codepoint_t glyph,
hb_draw_session_t &draw_session,
hb_array_t<const int> coords,
hb_codepoint_t parent_glyph = HB_CODEPOINT_INVALID,
hb_set_t *visited = nullptr,
signed *edges_left = nullptr,
signed depth_left = HB_MAX_NESTING_LEVEL) const
{
hb_set_t stack_set;
if (visited == nullptr)
visited = &stack_set;
signed stack_edges = HB_MAX_GRAPH_EDGE_COUNT;
if (edges_left == nullptr)
edges_left = &stack_edges;
// Don't recurse on the same glyph.
unsigned idx = glyph == parent_glyph ?
NOT_COVERED :
(this+coverage).get_coverage (glyph);
if (idx == NOT_COVERED)
{
if (!font->face->table.glyf->get_path_at (font, glyph, draw_session, coords))
#ifndef HB_NO_CFF
if (!font->face->table.cff2->get_path_at (font, glyph, draw_session, coords))
if (!font->face->table.cff1->get_path (font, glyph, draw_session)) // Doesn't have variations
#endif
return false;
return true;
}
if (depth_left <= 0)
return true;
if (*edges_left <= 0)
return true;
(*edges_left)--;
if (visited->has (glyph) || visited->in_error ())
return true;
visited->add (glyph);
hb_ubytes_t record = (this+glyphRecords)[idx];
VarRegionList::cache_t *cache = record.length >= 64 ? // Heuristic
(this+varStore).create_cache ()
: nullptr;
VarCompositeGlyph::get_path_at (font, glyph,
draw_session, coords,
record,
visited, edges_left, depth_left,
cache);
(this+varStore).destroy_cache (cache);
visited->del (glyph);
return true;
}
bool
get_path (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session) const
{ return get_path_at (font, gid, draw_session, hb_array (font->coords, font->num_coords)); }
bool paint_glyph (hb_font_t *font, hb_codepoint_t gid, hb_paint_funcs_t *funcs, void *data, hb_color_t foreground) const
{
funcs->push_clip_glyph (data, gid, font);
funcs->color (data, true, foreground);
funcs->pop_clip (data);
return true;
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
return_trace (version.sanitize (c) &&
hb_barrier () &&
version.major == 1 &&
coverage.sanitize (c, this) &&
varStore.sanitize (c, this) &&
conditionList.sanitize (c, this) &&
axisIndicesList.sanitize (c, this) &&
glyphRecords.sanitize (c, this));
}
protected:
FixedVersion<> version; /* Version identifier */
Offset32To<Coverage> coverage;
Offset32To<MultiItemVariationStore> varStore;
Offset32To<ConditionList> conditionList;
Offset32To<TupleList> axisIndicesList;
Offset32To<CFF2Index/*Of<VarCompositeGlyph>*/> glyphRecords;
public:
DEFINE_SIZE_STATIC (24);
};
#endif
//}
}
#endif /* OT_VAR_VARC_VARC_HH */
@@ -0,0 +1,37 @@
#ifndef OT_VAR_VARC_COORD_SETTER_HH
#define OT_VAR_VARC_COORD_SETTER_HH
#include "../../../hb.hh"
namespace OT {
//namespace Var {
struct coord_setter_t
{
coord_setter_t (hb_array_t<const int> coords) :
coords (coords) {}
int& operator [] (unsigned idx)
{
if (unlikely (idx >= HB_VAR_COMPOSITE_MAX_AXES))
return Crap(int);
if (coords.length < idx + 1)
coords.resize (idx + 1);
return coords[idx];
}
hb_array_t<int> get_coords ()
{ return coords.as_array (); }
hb_vector_t<int> coords;
};
//} // namespace Var
} // namespace OT
#endif /* OT_VAR_VARC_COORD_SETTER_HH */
@@ -0,0 +1,435 @@
#ifndef OT_GLYF_COMPOSITEGLYPH_HH
#define OT_GLYF_COMPOSITEGLYPH_HH
#include "../../hb-open-type.hh"
#include "composite-iter.hh"
namespace OT {
namespace glyf_impl {
struct CompositeGlyphRecord
{
protected:
enum composite_glyph_flag_t
{
ARG_1_AND_2_ARE_WORDS = 0x0001,
ARGS_ARE_XY_VALUES = 0x0002,
ROUND_XY_TO_GRID = 0x0004,
WE_HAVE_A_SCALE = 0x0008,
MORE_COMPONENTS = 0x0020,
WE_HAVE_AN_X_AND_Y_SCALE = 0x0040,
WE_HAVE_A_TWO_BY_TWO = 0x0080,
WE_HAVE_INSTRUCTIONS = 0x0100,
USE_MY_METRICS = 0x0200,
OVERLAP_COMPOUND = 0x0400,
SCALED_COMPONENT_OFFSET = 0x0800,
UNSCALED_COMPONENT_OFFSET = 0x1000,
#ifndef HB_NO_BEYOND_64K
GID_IS_24BIT = 0x2000
#endif
};
public:
unsigned int get_size () const
{
unsigned int size = min_size;
/* glyphIndex is 24bit instead of 16bit */
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT) size += HBGlyphID24::static_size - HBGlyphID16::static_size;
#endif
/* arg1 and 2 are int16 */
if (flags & ARG_1_AND_2_ARE_WORDS) size += 4;
/* arg1 and 2 are int8 */
else size += 2;
/* One x 16 bit (scale) */
if (flags & WE_HAVE_A_SCALE) size += 2;
/* Two x 16 bit (xscale, yscale) */
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) size += 4;
/* Four x 16 bit (xscale, scale01, scale10, yscale) */
else if (flags & WE_HAVE_A_TWO_BY_TWO) size += 8;
return size;
}
void drop_instructions_flag () { flags = (uint16_t) flags & ~WE_HAVE_INSTRUCTIONS; }
void set_overlaps_flag ()
{
flags = (uint16_t) flags | OVERLAP_COMPOUND;
}
bool has_instructions () const { return flags & WE_HAVE_INSTRUCTIONS; }
bool has_more () const { return flags & MORE_COMPONENTS; }
bool is_use_my_metrics () const { return flags & USE_MY_METRICS; }
bool is_anchored () const { return !(flags & ARGS_ARE_XY_VALUES); }
void get_anchor_points (unsigned int &point1, unsigned int &point2) const
{
const auto *p = &StructAfter<const HBUINT8> (flags);
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT)
p += HBGlyphID24::static_size;
else
#endif
p += HBGlyphID16::static_size;
if (flags & ARG_1_AND_2_ARE_WORDS)
{
point1 = ((const HBUINT16 *) p)[0];
point2 = ((const HBUINT16 *) p)[1];
}
else
{
point1 = p[0];
point2 = p[1];
}
}
static void transform (const float (&matrix)[4],
hb_array_t<contour_point_t> points)
{
if (matrix[0] != 1.f || matrix[1] != 0.f ||
matrix[2] != 0.f || matrix[3] != 1.f)
for (auto &point : points)
point.transform (matrix);
}
static void translate (const contour_point_t &trans,
hb_array_t<contour_point_t> points)
{
if (HB_OPTIMIZE_SIZE_VAL)
{
if (trans.x != 0.f || trans.y != 0.f)
for (auto &point : points)
point.translate (trans);
}
else
{
if (trans.x != 0.f && trans.y != 0.f)
for (auto &point : points)
point.translate (trans);
else
{
if (trans.x != 0.f)
for (auto &point : points)
point.x += trans.x;
else if (trans.y != 0.f)
for (auto &point : points)
point.y += trans.y;
}
}
}
void transform_points (hb_array_t<contour_point_t> points,
const float (&matrix)[4],
const contour_point_t &trans) const
{
if (scaled_offsets ())
{
translate (trans, points);
transform (matrix, points);
}
else
{
transform (matrix, points);
translate (trans, points);
}
}
bool get_points (contour_point_vector_t &points) const
{
float matrix[4];
contour_point_t trans;
get_transformation (matrix, trans);
if (unlikely (!points.alloc (points.length + 4))) return false; // For phantom points
points.push (trans);
return true;
}
unsigned compile_with_point (const contour_point_t &point,
char *out) const
{
const HBINT8 *p = &StructAfter<const HBINT8> (flags);
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT)
p += HBGlyphID24::static_size;
else
#endif
p += HBGlyphID16::static_size;
unsigned len = get_size ();
unsigned len_before_val = (const char *)p - (const char *)this;
if (flags & ARG_1_AND_2_ARE_WORDS)
{
// no overflow, copy value
hb_memcpy (out, this, len);
HBINT16 *o = reinterpret_cast<HBINT16 *> (out + len_before_val);
o[0] = roundf (point.x);
o[1] = roundf (point.y);
}
else
{
int new_x = roundf (point.x);
int new_y = roundf (point.y);
if (new_x <= 127 && new_x >= -128 &&
new_y <= 127 && new_y >= -128)
{
hb_memcpy (out, this, len);
HBINT8 *o = reinterpret_cast<HBINT8 *> (out + len_before_val);
o[0] = new_x;
o[1] = new_y;
}
else
{
// new point value has an int8 overflow
hb_memcpy (out, this, len_before_val);
//update flags
CompositeGlyphRecord *o = reinterpret_cast<CompositeGlyphRecord *> (out);
o->flags = flags | ARG_1_AND_2_ARE_WORDS;
out += len_before_val;
HBINT16 new_value;
new_value = new_x;
hb_memcpy (out, &new_value, HBINT16::static_size);
out += HBINT16::static_size;
new_value = new_y;
hb_memcpy (out, &new_value, HBINT16::static_size);
out += HBINT16::static_size;
hb_memcpy (out, p+2, len - len_before_val - 2);
len += 2;
}
}
return len;
}
protected:
bool scaled_offsets () const
{ return (flags & (SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET)) == SCALED_COMPONENT_OFFSET; }
public:
bool get_transformation (float (&matrix)[4], contour_point_t &trans) const
{
matrix[0] = matrix[3] = 1.f;
matrix[1] = matrix[2] = 0.f;
const auto *p = &StructAfter<const HBINT8> (flags);
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT)
p += HBGlyphID24::static_size;
else
#endif
p += HBGlyphID16::static_size;
int tx, ty;
if (flags & ARG_1_AND_2_ARE_WORDS)
{
tx = *(const HBINT16 *) p;
p += HBINT16::static_size;
ty = *(const HBINT16 *) p;
p += HBINT16::static_size;
}
else
{
tx = *p++;
ty = *p++;
}
if (is_anchored ()) tx = ty = 0;
/* set is_end_point flag to true, used by IUP delta optimization */
trans.init ((float) tx, (float) ty, true);
{
const F2DOT14 *points = (const F2DOT14 *) p;
if (flags & WE_HAVE_A_SCALE)
{
matrix[0] = matrix[3] = points[0].to_float ();
return true;
}
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE)
{
matrix[0] = points[0].to_float ();
matrix[3] = points[1].to_float ();
return true;
}
else if (flags & WE_HAVE_A_TWO_BY_TWO)
{
matrix[0] = points[0].to_float ();
matrix[1] = points[1].to_float ();
matrix[2] = points[2].to_float ();
matrix[3] = points[3].to_float ();
return true;
}
}
return tx || ty;
}
hb_codepoint_t get_gid () const
{
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT)
return StructAfter<const HBGlyphID24> (flags);
else
#endif
return StructAfter<const HBGlyphID16> (flags);
}
void set_gid (hb_codepoint_t gid)
{
#ifndef HB_NO_BEYOND_64K
if (flags & GID_IS_24BIT)
StructAfter<HBGlyphID24> (flags) = gid;
else
#endif
/* TODO assert? */
StructAfter<HBGlyphID16> (flags) = gid;
}
#ifndef HB_NO_BEYOND_64K
void lower_gid_24_to_16 ()
{
hb_codepoint_t gid = get_gid ();
if (!(flags & GID_IS_24BIT) || gid > 0xFFFFu)
return;
/* Lower the flag and move the rest of the struct down. */
unsigned size = get_size ();
char *end = (char *) this + size;
char *p = &StructAfter<char> (flags);
p += HBGlyphID24::static_size;
flags = flags & ~GID_IS_24BIT;
set_gid (gid);
memmove (p - HBGlyphID24::static_size + HBGlyphID16::static_size, p, end - p);
}
#endif
protected:
HBUINT16 flags;
HBUINT24 pad;
public:
DEFINE_SIZE_MIN (4);
};
using composite_iter_t = composite_iter_tmpl<CompositeGlyphRecord>;
struct CompositeGlyph
{
const GlyphHeader &header;
hb_bytes_t bytes;
CompositeGlyph (const GlyphHeader &header_, hb_bytes_t bytes_) :
header (header_), bytes (bytes_) {}
composite_iter_t iter () const
{ return composite_iter_t (bytes, &StructAfter<CompositeGlyphRecord, GlyphHeader> (header)); }
unsigned int instructions_length (hb_bytes_t bytes) const
{
unsigned int start = bytes.length;
unsigned int end = bytes.length;
const CompositeGlyphRecord *last = nullptr;
for (auto &item : iter ())
last = &item;
if (unlikely (!last)) return 0;
if (last->has_instructions ())
start = (char *) last - &bytes + last->get_size ();
if (unlikely (start > end)) return 0;
return end - start;
}
/* Trimming for composites not implemented.
* If removing hints it falls out of that. */
const hb_bytes_t trim_padding () const { return bytes; }
void drop_hints ()
{
for (const auto &_ : iter ())
const_cast<CompositeGlyphRecord &> (_).drop_instructions_flag ();
}
/* Chop instructions off the end */
void drop_hints_bytes (hb_bytes_t &dest_start) const
{ dest_start = bytes.sub_array (0, bytes.length - instructions_length (bytes)); }
void set_overlaps_flag ()
{
CompositeGlyphRecord& glyph_chain = const_cast<CompositeGlyphRecord &> (
StructAfter<CompositeGlyphRecord, GlyphHeader> (header));
if (!bytes.check_range(&glyph_chain, CompositeGlyphRecord::min_size))
return;
glyph_chain.set_overlaps_flag ();
}
bool compile_bytes_with_deltas (const hb_bytes_t &source_bytes,
const contour_point_vector_t &points_with_deltas,
hb_bytes_t &dest_bytes /* OUT */)
{
if (source_bytes.length <= GlyphHeader::static_size ||
header.numberOfContours != -1)
{
dest_bytes = hb_bytes_t ();
return true;
}
unsigned source_len = source_bytes.length - GlyphHeader::static_size;
/* try to allocate more memories than source glyph bytes
* in case that there might be an overflow for int8 value
* and we would need to use int16 instead */
char *o = (char *) hb_calloc (source_len * 2, sizeof (char));
if (unlikely (!o)) return false;
const CompositeGlyphRecord *c = reinterpret_cast<const CompositeGlyphRecord *> (source_bytes.arrayZ + GlyphHeader::static_size);
auto it = composite_iter_t (hb_bytes_t ((const char *)c, source_len), c);
char *p = o;
unsigned i = 0, source_comp_len = 0;
for (const auto &component : it)
{
/* last 4 points in points_with_deltas are phantom points and should not be included */
if (i >= points_with_deltas.length - 4) {
hb_free (o);
return false;
}
unsigned comp_len = component.get_size ();
if (component.is_anchored ())
{
hb_memcpy (p, &component, comp_len);
p += comp_len;
}
else
{
unsigned new_len = component.compile_with_point (points_with_deltas[i], p);
p += new_len;
}
i++;
source_comp_len += comp_len;
}
//copy instructions if any
if (source_len > source_comp_len)
{
unsigned instr_len = source_len - source_comp_len;
hb_memcpy (p, (const char *)c + source_comp_len, instr_len);
p += instr_len;
}
unsigned len = p - o;
dest_bytes = hb_bytes_t (o, len);
return true;
}
};
} /* namespace glyf_impl */
} /* namespace OT */
#endif /* OT_GLYF_COMPOSITEGLYPH_HH */
@@ -0,0 +1,556 @@
#ifndef OT_GLYF_GLYPH_HH
#define OT_GLYF_GLYPH_HH
#include "../../hb-open-type.hh"
#include "GlyphHeader.hh"
#include "SimpleGlyph.hh"
#include "CompositeGlyph.hh"
namespace OT {
struct glyf_accelerator_t;
namespace glyf_impl {
enum phantom_point_index_t
{
PHANTOM_LEFT = 0,
PHANTOM_RIGHT = 1,
PHANTOM_TOP = 2,
PHANTOM_BOTTOM = 3,
PHANTOM_COUNT = 4
};
struct Glyph
{
enum glyph_type_t {
EMPTY,
SIMPLE,
COMPOSITE,
};
public:
composite_iter_t get_composite_iterator () const
{
if (type != COMPOSITE) return composite_iter_t ();
return CompositeGlyph (*header, bytes).iter ();
}
const hb_bytes_t trim_padding () const
{
switch (type) {
case COMPOSITE: return CompositeGlyph (*header, bytes).trim_padding ();
case SIMPLE: return SimpleGlyph (*header, bytes).trim_padding ();
case EMPTY: return bytes;
default: return bytes;
}
}
void drop_hints ()
{
switch (type) {
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints (); return;
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints (); return;
case EMPTY: return;
}
}
void set_overlaps_flag ()
{
switch (type) {
case COMPOSITE: CompositeGlyph (*header, bytes).set_overlaps_flag (); return;
case SIMPLE: SimpleGlyph (*header, bytes).set_overlaps_flag (); return;
case EMPTY: return;
}
}
void drop_hints_bytes (hb_bytes_t &dest_start, hb_bytes_t &dest_end) const
{
switch (type) {
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints_bytes (dest_start); return;
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints_bytes (dest_start, dest_end); return;
case EMPTY: return;
}
}
bool is_composite () const
{ return type == COMPOSITE; }
bool get_all_points_without_var (const hb_face_t *face,
contour_point_vector_t &points /* OUT */) const
{
switch (type) {
case SIMPLE:
if (unlikely (!SimpleGlyph (*header, bytes).get_contour_points (points)))
return false;
break;
case COMPOSITE:
{
for (auto &item : get_composite_iterator ())
if (unlikely (!item.get_points (points))) return false;
break;
}
case EMPTY:
break;
}
/* Init phantom points */
if (unlikely (!points.resize (points.length + PHANTOM_COUNT))) return false;
hb_array_t<contour_point_t> phantoms = points.as_array ().sub_array (points.length - PHANTOM_COUNT, PHANTOM_COUNT);
{
int lsb = 0;
int h_delta = face->table.hmtx->get_leading_bearing_without_var_unscaled (gid, &lsb) ?
(int) header->xMin - lsb : 0;
HB_UNUSED int tsb = 0;
int v_orig = (int) header->yMax +
#ifndef HB_NO_VERTICAL
((void) face->table.vmtx->get_leading_bearing_without_var_unscaled (gid, &tsb), tsb)
#else
0
#endif
;
unsigned h_adv = face->table.hmtx->get_advance_without_var_unscaled (gid);
unsigned v_adv =
#ifndef HB_NO_VERTICAL
face->table.vmtx->get_advance_without_var_unscaled (gid)
#else
- face->get_upem ()
#endif
;
phantoms[PHANTOM_LEFT].x = h_delta;
phantoms[PHANTOM_RIGHT].x = (int) h_adv + h_delta;
phantoms[PHANTOM_TOP].y = v_orig;
phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv;
}
return true;
}
void update_mtx (const hb_subset_plan_t *plan,
int xMin, int xMax,
int yMin, int yMax,
const contour_point_vector_t &all_points) const
{
hb_codepoint_t new_gid = 0;
if (!plan->new_gid_for_old_gid (gid, &new_gid))
return;
if (type != EMPTY)
{
plan->bounds_width_vec[new_gid] = xMax - xMin;
plan->bounds_height_vec[new_gid] = yMax - yMin;
}
unsigned len = all_points.length;
float leftSideX = all_points[len - 4].x;
float rightSideX = all_points[len - 3].x;
float topSideY = all_points[len - 2].y;
float bottomSideY = all_points[len - 1].y;
uint32_t hash = hb_hash (new_gid);
signed hori_aw = roundf (rightSideX - leftSideX);
if (hori_aw < 0) hori_aw = 0;
int lsb = roundf (xMin - leftSideX);
plan->hmtx_map.set_with_hash (new_gid, hash, hb_pair ((unsigned) hori_aw, lsb));
//flag value should be computed using non-empty glyphs
if (type != EMPTY && lsb != xMin)
plan->head_maxp_info.allXMinIsLsb = false;
signed vert_aw = roundf (topSideY - bottomSideY);
if (vert_aw < 0) vert_aw = 0;
int tsb = roundf (topSideY - yMax);
plan->vmtx_map.set_with_hash (new_gid, hash, hb_pair ((unsigned) vert_aw, tsb));
}
bool compile_header_bytes (const hb_subset_plan_t *plan,
const contour_point_vector_t &all_points,
hb_bytes_t &dest_bytes /* OUT */) const
{
GlyphHeader *glyph_header = nullptr;
if (!plan->pinned_at_default && type != EMPTY && all_points.length >= 4)
{
glyph_header = (GlyphHeader *) hb_calloc (1, GlyphHeader::static_size);
if (unlikely (!glyph_header)) return false;
}
float xMin = 0, xMax = 0;
float yMin = 0, yMax = 0;
if (all_points.length > 4)
{
xMin = xMax = all_points[0].x;
yMin = yMax = all_points[0].y;
unsigned count = all_points.length - 4;
for (unsigned i = 1; i < count; i++)
{
float x = all_points[i].x;
float y = all_points[i].y;
xMin = hb_min (xMin, x);
xMax = hb_max (xMax, x);
yMin = hb_min (yMin, y);
yMax = hb_max (yMax, y);
}
}
// These are destined for storage in a 16 bit field to clamp the values to
// fit into a 16 bit signed integer.
int rounded_xMin = hb_clamp (roundf (xMin), -32768.0f, 32767.0f);
int rounded_xMax = hb_clamp (roundf (xMax), -32768.0f, 32767.0f);
int rounded_yMin = hb_clamp (roundf (yMin), -32768.0f, 32767.0f);
int rounded_yMax = hb_clamp (roundf (yMax), -32768.0f, 32767.0f);
update_mtx (plan, rounded_xMin, rounded_xMax, rounded_yMin, rounded_yMax, all_points);
if (type != EMPTY)
{
plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, rounded_xMin);
plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, rounded_yMin);
plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, rounded_xMax);
plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, rounded_yMax);
}
/* when pinned at default, no need to compile glyph header
* and for empty glyphs: all_points only include phantom points.
* just update metrics and then return */
if (!glyph_header)
return true;
glyph_header->numberOfContours = header->numberOfContours;
glyph_header->xMin = rounded_xMin;
glyph_header->yMin = rounded_yMin;
glyph_header->xMax = rounded_xMax;
glyph_header->yMax = rounded_yMax;
dest_bytes = hb_bytes_t ((const char *)glyph_header, GlyphHeader::static_size);
return true;
}
bool compile_bytes_with_deltas (const hb_subset_plan_t *plan,
hb_font_t *font,
const glyf_accelerator_t &glyf,
hb_bytes_t &dest_start, /* IN/OUT */
hb_bytes_t &dest_end /* OUT */)
{
contour_point_vector_t all_points, points_with_deltas;
unsigned composite_contours = 0;
head_maxp_info_t *head_maxp_info_p = &plan->head_maxp_info;
unsigned *composite_contours_p = &composite_contours;
// don't compute head/maxp values when glyph has no contours(type is EMPTY)
// also ignore .notdef glyph when --notdef-outline is not enabled
if (type == EMPTY ||
(gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE)))
{
head_maxp_info_p = nullptr;
composite_contours_p = nullptr;
}
if (!get_points (font, glyf, all_points, &points_with_deltas, head_maxp_info_p, composite_contours_p, false, false))
return false;
// .notdef, set type to empty so we only update metrics and don't compile bytes for
// it
if (gid == 0 &&
!(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE))
{
type = EMPTY;
dest_start = hb_bytes_t ();
dest_end = hb_bytes_t ();
}
//dont compile bytes when pinned at default, just recalculate bounds
if (!plan->pinned_at_default)
{
switch (type)
{
case COMPOSITE:
if (!CompositeGlyph (*header, bytes).compile_bytes_with_deltas (dest_start,
points_with_deltas,
dest_end))
return false;
break;
case SIMPLE:
if (!SimpleGlyph (*header, bytes).compile_bytes_with_deltas (all_points,
plan->flags & HB_SUBSET_FLAGS_NO_HINTING,
dest_end))
return false;
break;
case EMPTY:
/* set empty bytes for empty glyph
* do not use source glyph's pointers */
dest_start = hb_bytes_t ();
dest_end = hb_bytes_t ();
break;
}
}
if (!compile_header_bytes (plan, all_points, dest_start))
{
dest_end.fini ();
return false;
}
return true;
}
/* Note: Recursively calls itself.
* all_points includes phantom points
*/
template <typename accelerator_t>
bool get_points (hb_font_t *font, const accelerator_t &glyf_accelerator,
contour_point_vector_t &all_points /* OUT */,
contour_point_vector_t *points_with_deltas = nullptr, /* OUT */
head_maxp_info_t * head_maxp_info = nullptr, /* OUT */
unsigned *composite_contours = nullptr, /* OUT */
bool shift_points_hori = true,
bool use_my_metrics = true,
bool phantom_only = false,
hb_array_t<const int> coords = hb_array_t<const int> (),
hb_map_t *current_glyphs = nullptr,
unsigned int depth = 0,
unsigned *edge_count = nullptr) const
{
if (unlikely (depth > HB_MAX_NESTING_LEVEL)) return false;
unsigned stack_edge_count = 0;
if (!edge_count) edge_count = &stack_edge_count;
if (unlikely (*edge_count > HB_MAX_GRAPH_EDGE_COUNT)) return false;
(*edge_count)++;
hb_map_t current_glyphs_stack;
if (current_glyphs == nullptr)
current_glyphs = &current_glyphs_stack;
if (head_maxp_info)
{
head_maxp_info->maxComponentDepth = hb_max (head_maxp_info->maxComponentDepth, depth);
}
if (!coords)
coords = hb_array (font->coords, font->num_coords);
contour_point_vector_t stack_points;
contour_point_vector_t &points = type == SIMPLE ? all_points : stack_points;
unsigned old_length = points.length;
switch (type) {
case SIMPLE:
if (depth == 0 && head_maxp_info)
head_maxp_info->maxContours = hb_max (head_maxp_info->maxContours, (unsigned) header->numberOfContours);
if (depth > 0 && composite_contours)
*composite_contours += (unsigned) header->numberOfContours;
if (unlikely (!SimpleGlyph (*header, bytes).get_contour_points (all_points, phantom_only)))
return false;
break;
case COMPOSITE:
{
for (auto &item : get_composite_iterator ())
if (unlikely (!item.get_points (points))) return false;
break;
}
case EMPTY:
break;
}
/* Init phantom points */
if (unlikely (!points.resize (points.length + PHANTOM_COUNT))) return false;
hb_array_t<contour_point_t> phantoms = points.as_array ().sub_array (points.length - PHANTOM_COUNT, PHANTOM_COUNT);
{
int lsb = 0;
int h_delta = glyf_accelerator.hmtx->get_leading_bearing_without_var_unscaled (gid, &lsb) ?
(int) header->xMin - lsb : 0;
HB_UNUSED int tsb = 0;
int v_orig = (int) header->yMax +
#ifndef HB_NO_VERTICAL
((void) glyf_accelerator.vmtx->get_leading_bearing_without_var_unscaled (gid, &tsb), tsb)
#else
0
#endif
;
unsigned h_adv = glyf_accelerator.hmtx->get_advance_without_var_unscaled (gid);
unsigned v_adv =
#ifndef HB_NO_VERTICAL
glyf_accelerator.vmtx->get_advance_without_var_unscaled (gid)
#else
- font->face->get_upem ()
#endif
;
phantoms[PHANTOM_LEFT].x = h_delta;
phantoms[PHANTOM_RIGHT].x = (int) h_adv + h_delta;
phantoms[PHANTOM_TOP].y = v_orig;
phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv;
}
#ifndef HB_NO_VAR
if (coords)
glyf_accelerator.gvar->apply_deltas_to_points (gid,
coords,
points.as_array ().sub_array (old_length),
phantom_only && type == SIMPLE);
#endif
// mainly used by CompositeGlyph calculating new X/Y offset value so no need to extend it
// with child glyphs' points
if (points_with_deltas != nullptr && depth == 0 && type == COMPOSITE)
{
if (unlikely (!points_with_deltas->resize (points.length))) return false;
*points_with_deltas = points;
}
switch (type) {
case SIMPLE:
if (depth == 0 && head_maxp_info)
head_maxp_info->maxPoints = hb_max (head_maxp_info->maxPoints, all_points.length - old_length - 4);
break;
case COMPOSITE:
{
unsigned int comp_index = 0;
for (auto &item : get_composite_iterator ())
{
hb_codepoint_t item_gid = item.get_gid ();
if (unlikely (current_glyphs->has (item_gid)))
continue;
current_glyphs->add (item_gid);
unsigned old_count = all_points.length;
if (unlikely ((!phantom_only || (use_my_metrics && item.is_use_my_metrics ())) &&
!glyf_accelerator.glyph_for_gid (item_gid)
.get_points (font,
glyf_accelerator,
all_points,
points_with_deltas,
head_maxp_info,
composite_contours,
shift_points_hori,
use_my_metrics,
phantom_only,
coords,
current_glyphs,
depth + 1,
edge_count)))
{
current_glyphs->del (item_gid);
return false;
}
auto comp_points = all_points.as_array ().sub_array (old_count);
/* Copy phantom points from component if USE_MY_METRICS flag set */
if (use_my_metrics && item.is_use_my_metrics ())
for (unsigned int i = 0; i < PHANTOM_COUNT; i++)
phantoms[i] = comp_points[comp_points.length - PHANTOM_COUNT + i];
if (comp_points) // Empty in case of phantom_only
{
float matrix[4];
contour_point_t default_trans;
item.get_transformation (matrix, default_trans);
/* Apply component transformation & translation (with deltas applied) */
item.transform_points (comp_points, matrix, points[comp_index]);
}
if (item.is_anchored () && !phantom_only)
{
unsigned int p1, p2;
item.get_anchor_points (p1, p2);
if (likely (p1 < all_points.length && p2 < comp_points.length))
{
contour_point_t delta;
delta.init (all_points[p1].x - comp_points[p2].x,
all_points[p1].y - comp_points[p2].y);
item.translate (delta, comp_points);
}
}
all_points.resize (all_points.length - PHANTOM_COUNT);
if (all_points.length > HB_GLYF_MAX_POINTS)
{
current_glyphs->del (item_gid);
return false;
}
comp_index++;
current_glyphs->del (item_gid);
}
if (head_maxp_info && depth == 0)
{
if (composite_contours)
head_maxp_info->maxCompositeContours = hb_max (head_maxp_info->maxCompositeContours, *composite_contours);
head_maxp_info->maxCompositePoints = hb_max (head_maxp_info->maxCompositePoints, all_points.length);
head_maxp_info->maxComponentElements = hb_max (head_maxp_info->maxComponentElements, comp_index);
}
all_points.extend (phantoms);
} break;
case EMPTY:
all_points.extend (phantoms);
break;
}
if (depth == 0 && shift_points_hori) /* Apply at top level */
{
/* Undocumented rasterizer behavior:
* Shift points horizontally by the updated left side bearing
*/
float v = -phantoms[PHANTOM_LEFT].x;
if (v)
for (auto &point : all_points)
point.x += v;
}
return !all_points.in_error ();
}
bool get_extents_without_var_scaled (hb_font_t *font, const glyf_accelerator_t &glyf_accelerator,
hb_glyph_extents_t *extents) const
{
if (type == EMPTY) return true; /* Empty glyph; zero extents. */
return header->get_extents_without_var_scaled (font, glyf_accelerator, gid, extents);
}
hb_bytes_t get_bytes () const { return bytes; }
glyph_type_t get_type () const { return type; }
const GlyphHeader *get_header () const { return header; }
Glyph () : bytes (),
header (bytes.as<GlyphHeader> ()),
gid (-1),
type(EMPTY)
{}
Glyph (hb_bytes_t bytes_,
hb_codepoint_t gid_ = (unsigned) -1) : bytes (bytes_),
header (bytes.as<GlyphHeader> ()),
gid (gid_)
{
int num_contours = header->numberOfContours;
if (unlikely (num_contours == 0)) type = EMPTY;
else if (num_contours > 0) type = SIMPLE;
else if (num_contours <= -1) type = COMPOSITE;
else type = EMPTY; // Spec deviation; Spec says COMPOSITE, but not seen in the wild.
}
protected:
hb_bytes_t bytes;
const GlyphHeader *header;
hb_codepoint_t gid;
glyph_type_t type;
};
} /* namespace glyf_impl */
} /* namespace OT */
#endif /* OT_GLYF_GLYPH_HH */

Some files were not shown because too many files have changed in this diff Show More